Application plugins together wit Mime render in same extension

Hello everyone, I am new to Jupyterlab and trying to develop an extension.
At the moment I got as far as writing a panel showing some resources in a tree (similar to filebrowser) as shown in [1]. The resources are fetched from a REST call in the backend.
I also managed to implement dragging resources of of my tree view.
Now I’d like to make a dedicated Mimerender extension that takes care of showing a custom editor (will be a form that calls a service in the backend and returns some results that should be reusable in other notebooks).
I’d like to know if and how it is possible to develop the treeview plugin and the mimerender extension together into the same extension package. Because I was able to find only specific cookiecutters for either of them.
Moreover, in a more visionary approach, I’d be interested to know whether it is somehow possible to extend Ipython notebooks or in general other notebooks to make the form appear as a widget whenever I drop an item of my resource view on top of them.


Yes, this should be possible. The package.json jupyterlab key can give separate entry point files for each type of plugin:

"jupyterlab": {
  "extension": "path/to/application/extension.js",
  "mimeExtension": "path/to/mimerender/extension.js"

(see Extension Developer Guide — JupyterLab 3.1.6 documentation and Extension Developer Guide — JupyterLab 3.1.6 documentation)

1 Like

form appear as a widget

If you’re talking a Jupyter widget: I’ve used this adapter pattern to drive a widget, a document, and a mime renderer. I really need to abstract that out into something reusable!

As for the drag-and-drop behavior… I’d see it more like a snippet generator like dask-labextension, as doing bespoke things directly to the notebook is not super portable, whereas generating a little code (given a particular language) might be. So a user would:

  • open a notebook
  • open the sidebar
  • select one or more of your things
  • press button
  • get a new code cell that shows your things
  • executes the cell

Most of this would be reusing existing JupyterLab commands, and you could even expose your own command that did this. Further, if your API has more bells and whistles than your UI, a code-savvy user could use the snippet to bootstrap it in a more comfortable setting.

1 Like

Thanks to both of you @jasongrout and @bollwyvl your hints are pretty useful and putting me on the right track. Just one thing for @bollwyvl though… If I try to follow your recipe, what I understand is that generating snippets would be language (kernel) dependent, isn’t it? Instead I’d like to leverage REST APIs’ intrinsic interoperability to be able to exploit the provided functionality independently from the notebook’s kernel… A sort of form based widget that can be used in any notebook just to talk with my extension’s API.
Maybe I’m still to unaware of all the architectural aspects of JL though … so feel free to bully me if I told kinda BS :smiley:

Welp: we don’t really know what you’re trying to build, so just relying on experience of doing weird things that are sometimes surprising to users.

The client, kernel process and notebook process(es) are intentionally very separate concerns, and typically don’t share much directly… while the server (and therefore any server extension) knows where the kernel actually is, the client generally won’t have access to that information, directly. And the kernel will generally have no idea how to talk directly to the server’s REST API.

One can’t rely on most of the above pieces sharing the same environment variables, file system, user, IP address, port, or computer, as kernels and servers can be launched in all kinds of different ways, from a “traditional” localhost:8888 to JupyterHub, (Enterprise) Kernel Gateway. Then there’s totally insane stuff like JupyterLite.

When a kernel finally does appear to the user in the UI, which harmonizes all these possible deployment scenarios, things that appear in a notebook but aren’t computable by the kernel may well be very surprising to a user. There’s also no way that one could persist what was being done with the thing (whatever it is) unless it exists within the .ipynb, as saved: this can be achieved by manipulating the cells or the notebook’s metadata.

The snippet approach accepts that pretty much all “programming language” kernels (there are niche ones, like SQL) will probably be able to access a REST API (e.g. requests for python, curl for bash).

# snippet created with my-extension
import requests, IPython
my_data = requests.get("https://my-server/api/my-rest-api?token=abc1234").json()
IPython.display({"application/my-data+json": my_data}, raw=True)

…then your UI would be displayed, and the user would have access to the underlying data.

The higher-touch approach, with ipywidgets, would potentially be a better integration, but at the end of it, won’t provide much more computable value than the API, and can be hard to reason about wrapping a REST service with an evented API/GUI.

Yet another approach, if your endpoint has an OpenAPI description, would be to generate and ship language-specific bindings with e.g. swagger-codegen, but this brings a raft of other challenges.

Thanks to your valuable hints I’ve been able to resolve all my functional requirements. Now I’m able to download a list of resources from my API, show them in a tree that opens in the left sidebar. Drag out one item and drop it to the workarea. This causes a configuration form to appear based on the item’s characteristics. After inputing values I can call my api to “execute” the item and download files to the drive.
As per your suggestions, I’ve implemented the “click on a button” to generate few lines of python code that can be pasted into a notebook cell obtaining the same results in a notebook.
Now I can start with polishing the look and feel of the configuration form and I’am going to post a new question in a dedicated topic.
THANK YOU very much for the support on this.

This sounds like a very useful pattern of interaction! If it is open-source, do you have a link? (If not, that’s fine too, of course). This sort of pattern of providing the user with some choices, configuration, then action, as well as a way to programmatically do the same thing, is something that I think a lot of people would be very interested in seeing and modifying for their usecases.

Thanks. I’m still experimenting but it feels rather comfortable to use (even if still ugly to watch at because too little css so far :-D).
As soon as I refactor and package it as an extension it will be released for sure under a permissive OSS license and I’ll post back here.

1 Like

Just getting back in my armchair for some architecting here: As the heaviest dependency, i guess this feature is something in @jupyter-widgets could own. If the widget jupyterlab-manager extension knew how to draw a widget manager state, serialized as JSON (e.g. .jupyter-widgets), or widgets could otherwise declare a mime type and start becoming a file editor… but there would still be a lot of assumptions that might not be true (like being part of a notebook DOM, or even a kernel).

Further, while the document/mime form “just works” with text-based yjs collaboration, there would probably be a benefit from some higher-order things that allowed for using structural collaboration (like the notebook in core, and jupyterlab-drawio's XML)… I’m still looking for that schema-driven pattern, where it doesn’t take unique JS every time.