<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Jupyter Blog - extensions</title><link href="https://blog.jupyter.org/" rel="alternate"/><link href="https://blog.jupyter.org/feeds/tag-extensions.atom.xml" rel="self"/><id>https://blog.jupyter.org/</id><updated>2026-09-22T00:37:00+00:00</updated><subtitle>Extensions for JupyterLab, Jupyter Notebook, and Jupyter Server.</subtitle><entry><title>Notebooks-on-Demand</title><link href="https://blog.jupyter.org/posts/2026/notebooks-on-demand-nod-a-jupyterlab-extension-for-a/" rel="alternate"/><published>2026-09-22T00:37:00+00:00</published><updated>2026-09-22T00:37:00+00:00</updated><author><name>Eric Rawn</name></author><id>tag:blog.jupyter.org,2026-09-22:/posts/2026/notebooks-on-demand-nod-a-jupyterlab-extension-for-a/</id><summary type="html">&lt;p&gt;Jump into a Jupyter notebook from anywhere in a Python codebase with this open-source JupyterLab extension.&lt;/p&gt;
</summary><content type="html">&lt;figure&gt;
&lt;iframe src="https://www.youtube-nocookie.com/embed/xgsp8h8bvfI" title="notebooks on demand demo" width="560" height="315" style="aspect-ratio: 560 / 315" loading="lazy" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;
&lt;figcaption&gt;
&lt;p&gt;A tour of Notebooks-On-Demand&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a href="https://github.com/erawn/nod"&gt;&lt;em&gt;Nod: Notebooks-on-Demand&lt;/em&gt;&lt;/a&gt; is an open-source JupyterLab extension that lets you jump into a Jupyter notebook from anywhere in a Python codebase. Just call &lt;code&gt;notebook()&lt;/code&gt; from anywhere in your &lt;code&gt;.py&lt;/code&gt; file, run it with &lt;code&gt;nod&lt;/code&gt;, and a notebook opens at that point in the program with all your variables already loaded into the notebook state. When you’re done making edits, send them back to your original Python file with a click.&lt;/p&gt;
&lt;p&gt;Here’s what it looks like in practice. Say you have this Python program:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;nodpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;notebook&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;notebook&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;#our program will pause here&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;All you need to do is execute your program with &lt;code&gt;nod&lt;/code&gt; at the start:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;nod&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;myfile
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;and a new JupyterLab session will open which looks like this:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Left: The Nod Control Panel shows the call stack for our notebook() call, Center: The notebook created from the current Nod Session, Right: The JupyterLab Debugger shows the current variables in our kernel" src="https://blog.jupyter.org/posts/2026/notebooks-on-demand-nod-a-jupyterlab-extension-for-a/images/001-1_aepH_Tg-Y9filKEEllMe4g.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Left: The Nod Control Panel shows the call stack for our &lt;code&gt;notebook()&lt;/code&gt; call, Center: The notebook created from the current Nod Session, Right: The JupyterLab Debugger shows the current variables in our kernel&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;You’ll notice a couple of things. First, on the right-hand pane, you’ll see that the current notebook state matches the variables available in the Python program at the place &lt;code&gt;notebook()&lt;/code&gt; was called. Second, the body of the current function &lt;code&gt;f&lt;/code&gt; is editable, and the surrounding code is displayed for context.&lt;/p&gt;
&lt;p&gt;From here, you can use your on-demand notebook to write and debug the function &lt;code&gt;f&lt;/code&gt;. When you’re done, you can send your new code back to the original source file with a button at the top of the Nod Panel on the left (see the &lt;a href="https://github.com/erawn/nod"&gt;repository&lt;/a&gt; for detailed instructions). Whatever you wrote in your on-demand notebook will replace the current body of &lt;code&gt;f&lt;/code&gt; in the source file. The notebook cells will be converted to newlines, and markdown cells are converted to comments (thanks to &lt;a href="https://github.com/jupytext/jupytext"&gt;Jupytext&lt;/a&gt;!). We can also re-run our program from the beginning with these new changes in our source file through the Nod panel.&lt;/p&gt;
&lt;p&gt;Here’s a few scenarios that Nod might be helpful:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Scenario 1: You’re working in a large Python project and code is spread over countless files, classes, and function calls. You begin writing a new function, but the arguments are complex and questions abound: &lt;em&gt;What exactly does that class method return&lt;/em&gt;? &lt;em&gt;When I join these two tables, what does the output look like? How do I even construct the inputs to this function?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You think: &lt;em&gt;This would be so much easier if I could just write this function in a notebook!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Instead of manually copying out code snippets, writing data structures to files and reading them into a notebook, and perhaps getting something to work eventually,&lt;/em&gt; you can just call &lt;code&gt;notebook()&lt;/code&gt;, &lt;em&gt;and a Jupyter notebook appears with just the body of your function and the function’s arguments already in your notebook state.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Scenario 2: &lt;em&gt;You realize there’s a bug in a specific function when called with specific arguments. You could use a&lt;/em&gt; &lt;code&gt;breakpoint()&lt;/code&gt; &lt;em&gt;to inspect the program, but you don’t just want to look at the arguments, you want to see if different ways of changing the arguments remove the bug when you run the function code. Instead, you call&lt;/em&gt; &lt;code&gt;notebook()&lt;/code&gt; &lt;em&gt;when the function is invoked with the buggy arguments, opening a Jupyter notebook on the function definition with those arguments loaded in the notebook state. You can modify the arguments and rerun your code in the notebook to test different outcomes and implement your fix. When you’re done, you can send the changes back to your source file and restart (opening a notebook-on-demand for your new program), verifying that your program is now running as expected.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Scenario 3: You use an AI system to generate some code, but there’s a crucial part you want to really understand. Just call &lt;code&gt;notebook()&lt;/code&gt; at that part, and now you have the full functionality of a notebook to walk line-by-line through the code, printing and visualizing the output at each step or experimenting with alternative decisions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Nod’s on-demand notebooks are &lt;em&gt;&lt;strong&gt;designed to be disposable&lt;/strong&gt;&lt;/em&gt;. After you’ve sent your changes back to your source file, all the information to recreate the notebook is already in your Python file, so you can safely quit the JupyterLab session and continue programming in your text editor of choice. Your code can live in your text editor and Python source files, but Nod makes a quick trip to a notebook easy.&lt;/p&gt;
&lt;p&gt;While I’ve found this workflow by itself to be really helpful in my own programming, Nod can do two other things that are pretty neat:&lt;/p&gt;
&lt;p&gt;First, you don’t just have to stay in the function body where you called &lt;code&gt;notebook()&lt;/code&gt;; you can also navigate up and down the call stack with the Nod panel on the left. For example, you can select the function &lt;code&gt;d&lt;/code&gt; to see how &lt;code&gt;f&lt;/code&gt; was called:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="The Nod Panel on the Left navigates down the callstack, replacing the current notebook and the state to match." src="https://blog.jupyter.org/posts/2026/notebooks-on-demand-nod-a-jupyterlab-extension-for-a/images/002-1_MB1vHj5UbqOnMG8FxjXLeQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;The Nod Panel on the Left navigates down the callstack, replacing the current notebook and the state to match.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Both our notebook and the current variables update to our new position in our program (&lt;code&gt;x&lt;/code&gt; isn’t in our debugger pane on the right anymore, since it’s not available within function &lt;code&gt;d&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Second, you can use &lt;code&gt;nodLog()&lt;/code&gt; to collect a bunch of values from your program to poke and prod in an on-demand notebook. Take a look at the following file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;nodpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;notebook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nodLog&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;notebook&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;nodLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Running it will pop open a JupyterLab interface where, on the right, you’ll have access to a “Nod Log” pane, which lets you select assignments to &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; for the on-demand notebook session among the values you saved with &lt;code&gt;nodLog()&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="nodLog() captures variables while your program runs and makes them available in the on-demand notebook session to experiment with." src="https://blog.jupyter.org/posts/2026/notebooks-on-demand-nod-a-jupyterlab-extension-for-a/images/003-1_6GAT730KwNVY8NJDM2dOLw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;&lt;code&gt;nodLog()&lt;/code&gt; captures variables while your program runs and makes them available in the on-demand notebook session to experiment with.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When working with Python objects which can vary in unpredictable ways (like the results of a JSON API call or some other heavily-nested Python dictionary), I find &lt;code&gt;nodLog()&lt;/code&gt; to be helpful in gauging the possible values I need to handle when writing a new function. It lets me inspect and switch between different inputs to my function easily.&lt;/p&gt;
&lt;p&gt;Nod is free and open-source, so &lt;a href="https://github.com/erawn/nod"&gt;check it out here&lt;/a&gt; if you’re interested. Nod also works with JupyterHub with a slightly different workflow (see the repository for a guide!). My goal with Nod is to add to the situations where Jupyter Notebooks are a useful tool. If you have ideas about more ways Nod can be helpful or feedback to make it better, please leave an issue on the Github page and we can discuss!&lt;/p&gt;
&lt;div class="main-content-end" aria-hidden="true"&gt;&lt;/div&gt;
</content><category term="debugging"/><category term="extensions"/><category term="JupyterLab"/></entry><entry><title>Announcing jupyter-builder: A Standalone Build System for JupyterLab Extensions</title><link href="https://blog.jupyter.org/posts/2026/announcing-jupyter-builder-a-standalone-build-system/" rel="alternate"/><published>2026-06-19T13:11:00+00:00</published><updated>2026-06-19T13:11:00+00:00</updated><author><name>Darshan Kr. Paudyal</name></author><id>tag:blog.jupyter.org,2026-06-19:/posts/2026/announcing-jupyter-builder-a-standalone-build-system/</id><summary type="html">&lt;p&gt;We’re excited to announce the first stable release of jupyter-builder on PyPI and @jupyter/builder on npm. A standalone, configurable build system for JupyterLab and its extensions.&lt;/p&gt;</summary><content type="html">&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2026/announcing-jupyter-builder-a-standalone-build-system/images/001-1_3UBtK-sh01N6OXIbGFNrpQ.webp" alt="Jupyter Builder logo" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;We’re excited to announce the first stable release of &lt;a href="https://pypi.org/project/jupyter-builder/"&gt;&lt;code&gt;jupyter-builder&lt;/code&gt;&lt;/a&gt; on PyPI and &lt;a href="https://www.npmjs.com/package/@jupyter/builder"&gt;&lt;code&gt;@jupyter/builder&lt;/code&gt;&lt;/a&gt; on npm. A standalone, configurable build system for JupyterLab and its extensions.&lt;/p&gt;
&lt;p&gt;If you build or maintain a JupyterLab extension, this release is for you.&lt;/p&gt;
&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;
&lt;p&gt;For a long time, building a JupyterLab extension has meant installing all of JupyterLab. Take a look at any extension’s build dependencies and you’ll see &lt;code&gt;jupyterlab&lt;/code&gt; listed there, not because the extension needs JupyterLab at runtime to be built, but because the build tooling lives &lt;em&gt;inside&lt;/em&gt; the JupyterLab repository. The &lt;code&gt;builder/&lt;/code&gt; folder, the &lt;code&gt;jlpm&lt;/code&gt; command, the helper scripts, they’re all coupled to the JupyterLab core.&lt;/p&gt;
&lt;p&gt;This setup has caused real friction over the years:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Linux packagers&lt;/strong&gt; have repeatedly raised concerns about the circular dependency where extensions like &lt;code&gt;jupyterlab_pygments&lt;/code&gt; need &lt;code&gt;jupyterlab&lt;/code&gt; to build, but &lt;code&gt;jupyterlab&lt;/code&gt; ships with those extensions. It’s a chicken-and-egg problem that makes packaging painful.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extension authors&lt;/strong&gt; have to update their build system every time they update to a new JupyterLab version, which sometimes introduces breakages that have nothing to do with their extension’s actual code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Users&lt;/strong&gt; still occasionally run into installation issues that trace back to the incomplete migration from “source” extensions to prebuilt ones.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The need to separate the build system was identified back in &lt;a href="https://github.com/jupyterlab/jupyterlab/issues/13456"&gt;jupyterlab#13456&lt;/a&gt;, and initial work began during GSoC 2024. With the support of the Jupyter Foundation’s first community-funded proposal, we’ve now brought that work to a stable release.&lt;/p&gt;
&lt;h2 id="what-jupyter-builder-does"&gt;What &lt;code&gt;jupyter-builder&lt;/code&gt; does&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;jupyter-builder&lt;/code&gt; extracts all the Node.js-based build tooling out of JupyterLab into a dedicated package. You install it on its own, and it builds your extension, no full JupyterLab installation required.&lt;/p&gt;
&lt;p&gt;It ships in two parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/jupyter-builder/"&gt;&lt;strong&gt;&lt;code&gt;jupyter-builder&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt; (PyPI) — the Python package that provides the &lt;code&gt;jupyter-builder&lt;/code&gt; CLI.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@jupyter/builder"&gt;&lt;strong&gt;&lt;code&gt;@jupyter/builder&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt; (npm) — the underlying JavaScript package containing the build scripts. The Python CLI invokes &lt;code&gt;build-labextension.js&lt;/code&gt; from this package, which then runs the &lt;a href="https://rspack.dev/"&gt;rspack&lt;/a&gt; compiler with all the configuration needed to produce a JupyterLab extension.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of &lt;code&gt;jupyter labextension build .&lt;/code&gt;, you now run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;jupyter-builder&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="build-compatibility-without-jupyterlab-installed"&gt;Build compatibility without JupyterLab installed&lt;/h2&gt;
&lt;p&gt;One interesting problem we had to solve: if JupyterLab isn’t installed, how does the builder verify that an extension is compatible with a given JupyterLab version?&lt;/p&gt;
&lt;p&gt;The answer is &lt;a href="https://www.npmjs.com/package/@jupyterlab/core-meta"&gt;&lt;code&gt;@jupyterlab/core-meta&lt;/code&gt;&lt;/a&gt;, a small npm package that publishes JupyterLab’s core metadata on its own. &lt;code&gt;@jupyter/builder&lt;/code&gt; declares a dependency on a specific &lt;code&gt;@jupyterlab/core-meta&lt;/code&gt; version, so by default your extension is checked for compatibility against whatever JupyterLab version that pin corresponds to. In general, the latest &lt;code&gt;@jupyter/builder&lt;/code&gt; will track the latest JupyterLab.&lt;/p&gt;
&lt;p&gt;If you want to build against a different JupyterLab version, pass &lt;code&gt;--core-version&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;jupyter-builder&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;--core-version&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.4.x
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We’ll download that version’s metadata from &lt;code&gt;@jupyterlab/core-meta&lt;/code&gt; on npm and check compatibility against it.&lt;/p&gt;
&lt;h2 id="migrating-an-existing-extension"&gt;Migrating an existing extension&lt;/h2&gt;
&lt;p&gt;If you’re starting a new extension with the &lt;a href="https://github.com/jupyterlab/extension-template"&gt;extension template&lt;/a&gt;, you’ll get all of this set up automatically. For existing extensions, there are two changes to make.&lt;/p&gt;
&lt;h3 id="1-update-the-python-build-dependencies"&gt;1. Update the Python build dependencies&lt;/h3&gt;
&lt;p&gt;In your &lt;code&gt;pyproject.toml&lt;/code&gt;, swap &lt;code&gt;jupyterlab&lt;/code&gt; for &lt;code&gt;jupyter-builder&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before&lt;/span&gt;
&lt;span class="na"&gt;requires&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;[&amp;quot;hatchling&amp;gt;=1.5.0&amp;quot;, &amp;quot;jupyterlab&amp;gt;=4.0.0,&amp;lt;5&amp;quot;, &amp;quot;hatch-nodejs-version&amp;gt;=0.3.2&amp;quot;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# After&lt;/span&gt;
&lt;span class="na"&gt;requires&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;[&amp;quot;hatchling&amp;gt;=1.5.0&amp;quot;, &amp;quot;jupyter-builder&amp;gt;=1.0.0&amp;quot;, &amp;quot;hatch-nodejs-version&amp;gt;=0.3.2&amp;quot;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then update the &lt;code&gt;scripts&lt;/code&gt; section of your &lt;code&gt;package.json&lt;/code&gt; to use the new CLI:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;quot;build:labextension&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;jupyter-builder build .&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;&amp;quot;build:labextension:dev&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;jupyter-builder build --development True .&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;&amp;quot;watch:labextension&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;jupyter-builder watch .&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Your build no longer pulls in all of JupyterLab.&lt;/p&gt;
&lt;h3 id="2-update-the-npm-builder-dependency"&gt;2. Update the npm builder dependency&lt;/h3&gt;
&lt;p&gt;Replace &lt;a href="https://www.npmjs.com/package/@jupyterlab/builder"&gt;&lt;code&gt;@jupyterlab/builder&lt;/code&gt;&lt;/a&gt; with &lt;code&gt;@jupyter/builder&lt;/code&gt; in your &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;quot;@jupyter/builder&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^1.0.0&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;jupyter-builder&lt;/code&gt; is backwards-compatible with &lt;a href="https://www.npmjs.com/package/@jupyterlab/builder"&gt;&lt;code&gt;@jupyterlab/builder&lt;/code&gt;&lt;/a&gt;, so things will keep working if you don’t migrate immediately. That said, we strongly recommend switching: &lt;a href="https://www.npmjs.com/package/@jupyterlab/builder"&gt;&lt;code&gt;@jupyterlab/builder&lt;/code&gt;&lt;/a&gt; is no longer being actively developed, and all new work is happening in &lt;a href="https://www.npmjs.com/package/@jupyter/builder"&gt;&lt;code&gt;@jupyter/builder&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Note that as part of this transition JupyterLab has also moved from Webpack to &lt;a href="https://rspack.rs/"&gt;Rspack&lt;/a&gt;. Most extensions won’t need any changes, but if yours uses a custom &lt;code&gt;webpackConfig&lt;/code&gt; see the &lt;a href="https://jupyterlab.readthedocs.io/en/latest/extension/extension_migration.html#building-extensions-with-rspack"&gt;Rspack migration notes&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What’s next&lt;/h2&gt;
&lt;p&gt;This release is the first stable foundation. We’re continuing to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Migrate more packages across the &lt;a href="https://github.com/jupyterlab/"&gt;Jupyter organization&lt;/a&gt; to use &lt;code&gt;jupyter-builder&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Make the builder more configurable, so it can eventually be used for projects beyond JupyterLab.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you maintain an extension, please try the migration and &lt;a href="https://github.com/jupyterlab/jupyter-builder/issues"&gt;let us know how it goes&lt;/a&gt;. Feedback from real extensions is the best way to make sure the migration path stays smooth.&lt;/p&gt;
&lt;div class="main-content-end" aria-hidden="true"&gt;&lt;/div&gt;
&lt;h2 id="acknowledgements"&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;This work was funded under the &lt;a href="https://blog.jupyter.org/posts/2025/announcing-our-first-jupyter-community-funded-proposals/"&gt;first round of Jupyter Community Funded Proposals&lt;/a&gt;. We want to thank the &lt;a href="https://jupyterfoundation.org/"&gt;Jupyter Foundation&lt;/a&gt; and everyone involved in setting up this funding mechanism.&lt;/p&gt;
&lt;p&gt;A huge thank you to &lt;a href="https://github.com/bollwyvl"&gt;Nicholas Bollweg&lt;/a&gt; for many thoughtful discussions on the design and packaging of &lt;code&gt;jupyter-builder&lt;/code&gt;, and to &lt;a href="https://github.com/jtpio"&gt;Jeremy Tuloup&lt;/a&gt; for helping us resolve issues and for guiding the adoption of &lt;code&gt;jupyter-builder&lt;/code&gt; in Jupyter Notebook.&lt;/p&gt;
&lt;p&gt;This package was initially created during &lt;a href="https://summerofcode.withgoogle.com/archive/2024/projects/YMB3Rc53"&gt;GSoC 2024&lt;/a&gt; by &lt;a href="https://www.linkedin.com/in/ronan-coutinho/"&gt;Ronan Coutinho&lt;/a&gt;, mentored by &lt;a href="https://www.linkedin.com/in/fcollonval/"&gt;Frédéric Collonval&lt;/a&gt;. Their work laid the foundation that made this release possible.&lt;/p&gt;
&lt;h2 id="about-the-developers"&gt;About the developers&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/darshan-poudel8/"&gt;&lt;strong&gt;Darshan Paudyal&lt;/strong&gt;&lt;/a&gt; is an intern at &lt;a href="https://openteams.com/"&gt;OpenTeams&lt;/a&gt; and a member of the &lt;a href="https://jupyterlab-team-compass.readthedocs.io/en/latest/team.html"&gt;Jupyter Frontend Council&lt;/a&gt;. Darshan led the work to bring &lt;code&gt;jupyter-builder&lt;/code&gt; to a stable release, completing the separation from JupyterLab core and shepherding the package through to its first published version.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/michal-krassowski/"&gt;&lt;strong&gt;Michał Krassowski&lt;/strong&gt;&lt;/a&gt; is a Senior Software Engineer at &lt;a href="https://openteams.com/"&gt;OpenTeams&lt;/a&gt;. Mike provided guidance and technical direction throughout the development of &lt;code&gt;jupyter-builder&lt;/code&gt;, helping shape its architecture and roadmap.&lt;/p&gt;
&lt;h2 id="useful-links"&gt;Useful links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📦 PyPI: &lt;a href="https://pypi.org/project/jupyter-builder/"&gt;&lt;code&gt;jupyter-builder&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;📦 npm: &lt;a href="https://www.npmjs.com/package/@jupyter/builder"&gt;&lt;code&gt;@jupyter/builder&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;💻 Source: &lt;a href="https://github.com/jupyterlab/jupyter-builder"&gt;github.com/jupyterlab/jupyter-builder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;📖 Migration guide: &lt;a href="https://jupyterlab.readthedocs.io/en/latest/extension/extension_migration.html#migrating-to-jupyter-builder"&gt;&lt;code&gt;jupyter-builder&lt;/code&gt; migration section&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;💬 Issues &amp;amp; feedback: &lt;a href="https://github.com/jupyterlab/jupyter-builder/issues"&gt;GitHub issues&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content><category term="extensions"/><category term="JupyterLab"/></entry><entry><title>Catching Jupyter-specific bugs before CI does: announcing jupyter eslint plugin</title><link href="https://blog.jupyter.org/posts/2026/catching-jupyter-specific-bugs-before-ci-does/" rel="alternate"/><published>2026-06-11T15:13:00+00:00</published><updated>2026-06-11T15:13:00+00:00</updated><author><name>Darshan Kr. Paudyal</name></author><id>tag:blog.jupyter.org,2026-06-11:/posts/2026/catching-jupyter-specific-bugs-before-ci-does/</id><summary type="html">&lt;p&gt;We’re excited to announce the release of the first version of the Jupyter ESLint plugin @jupyter/eslint-plugin, now available on npm. It’s a custom ESLint plugin designed specifically for the official Jupyter frontends (JupyterLab, Notebook, JupyterLite) and TypeScript extensions built around them.&lt;/p&gt;</summary><content type="html">&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2026/catching-jupyter-specific-bugs-before-ci-does/images/001-1_CXmGuOa5O6Z60l4KZSEo3g.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;We’re excited to announce the release of the first version of the Jupyter ESLint plugin &lt;a href="https://www.npmjs.com/package/@jupyter/eslint-plugin"&gt;&lt;code&gt;@jupyter/eslint-plugin&lt;/code&gt;&lt;/a&gt;, now available on npm. It’s a custom ESLint plugin designed specifically for the official Jupyter frontends (JupyterLab, Notebook, JupyterLite) and TypeScript extensions built around them.&lt;/p&gt;
&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;
&lt;p&gt;If you’ve ever written a JupyterLab extension, you’ve probably hit something like this:&lt;/p&gt;
&lt;p&gt;You wire up a new plugin, push a PR, walk away to make coffee, and 30 minutes later CI fails on a test you didn’t expect to be related. You scroll through logs, eventually trace it back, and realize the problem was a single misaligned argument in your &lt;code&gt;activate&lt;/code&gt; function. TypeScript was happy. The editor showed no red squiggles. Nothing in your local toolchain said a word.&lt;/p&gt;
&lt;p&gt;And the slow CI feedback loop is only half the story. Some of these mistakes don’t fail CI at all, a hardcoded English &lt;code&gt;label&lt;/code&gt;, an &lt;code&gt;enum&lt;/code&gt; in a settings schema, a malformed token id. They pass tests, ship to users, and only surface at runtime. They’re the kind of bugs you hear about from users months later, if at all.&lt;/p&gt;
&lt;p&gt;JupyterLab, Jupyter Notebook, and the wider extension ecosystem follow a lot of project-specific patterns around plugins, commands, tokens, settings schemas, translations, and visual tests. These patterns aren’t enforced by TypeScript, because they live one level above the type system. They’re contracts about &lt;em&gt;how&lt;/em&gt; the framework expects code to be written, not about whether the code type-checks.&lt;/p&gt;
&lt;p&gt;A few examples of what generic linters and TypeScript can’t catch:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;JupyterFrontEndPlugin&lt;/code&gt; that requires &lt;code&gt;[TokenA, TokenB]&lt;/code&gt; but whose &lt;code&gt;activate&lt;/code&gt; function takes those arguments in the wrong order. Both are tokens, both type-check but the plugin fails at activation.&lt;/li&gt;
&lt;li&gt;A command added with &lt;code&gt;commands.addCommand(...)&lt;/code&gt; whose &lt;code&gt;label&lt;/code&gt; is a hardcoded English string. The extension ships, gets translated by the community, and that one button silently stays in English.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are real, recurring issues. They cost contributor time, they frustrate first-time contributors, and they pile onto reviewer load. They’re exactly the kind of knowledge that lives in the heads of long-time maintainers and exactly the kind of knowledge that a linter is great at encoding. So we built one.&lt;/p&gt;
&lt;h2 id="what-v1-catches"&gt;What v1 catches&lt;/h2&gt;
&lt;p&gt;The plugin currently ships eight rules, grouped by what they protect:&lt;/p&gt;
&lt;h3 id="plugin-structure"&gt;Plugin structure&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://eslint-plugin.readthedocs.io/en/latest/rules/plugin-activation-args/"&gt;&lt;code&gt;plugin-activation-args&lt;/code&gt;&lt;/a&gt; — Keeps &lt;code&gt;activate&lt;/code&gt; function arguments aligned with &lt;code&gt;requires&lt;/code&gt; and &lt;code&gt;optional&lt;/code&gt; tokens.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eslint-plugin.readthedocs.io/en/latest/rules/plugin-description/"&gt;&lt;code&gt;plugin-description&lt;/code&gt;&lt;/a&gt; — Requires a non-empty &lt;code&gt;description&lt;/code&gt; on every &lt;code&gt;JupyterFrontEndPlugin&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eslint-plugin.readthedocs.io/en/latest/rules/token-format/"&gt;&lt;code&gt;token-format&lt;/code&gt;&lt;/a&gt; — Enforces the &lt;code&gt;&amp;lt;package&amp;gt;:&amp;lt;TokenSymbol&amp;gt;&lt;/code&gt; convention for &lt;code&gt;new Token(...)&lt;/code&gt; ids.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="commands-and-settings"&gt;Commands and settings&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://eslint-plugin.readthedocs.io/en/latest/rules/command-described-by/"&gt;&lt;code&gt;command-described-by&lt;/code&gt;&lt;/a&gt; — Requires a &lt;code&gt;describedBy&lt;/code&gt; schema on commands that accept &lt;code&gt;args&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eslint-plugin.readthedocs.io/en/latest/rules/no-schema-enum/"&gt;&lt;code&gt;no-schema-enum&lt;/code&gt;&lt;/a&gt; — Disallows &lt;code&gt;enum&lt;/code&gt; in settings JSON schemas in favor of translatable &lt;code&gt;oneOf&lt;/code&gt; with &lt;code&gt;const&lt;/code&gt; and &lt;code&gt;title&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="internationalization"&gt;Internationalization&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://eslint-plugin.readthedocs.io/en/latest/rules/no-untranslated-string/"&gt;&lt;code&gt;no-untranslated-string&lt;/code&gt;&lt;/a&gt; — Flags hardcoded user-facing strings in command labels, dialogs, accessibility attributes, JSX, and more.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eslint-plugin.readthedocs.io/en/latest/rules/no-translation-concatenation/"&gt;&lt;code&gt;no-translation-concatenation&lt;/code&gt;&lt;/a&gt; — Forbids dynamic concatenation inside &lt;code&gt;trans.__()&lt;/code&gt; calls that translation extractors can’t pick up.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="testing"&gt;Testing&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://eslint-plugin.readthedocs.io/en/latest/rules/require-soft-assertions-before-snapshots/"&gt;&lt;code&gt;require-soft-assertions-before-snapshots&lt;/code&gt;&lt;/a&gt; — Requires &lt;code&gt;expect.soft(...)&lt;/code&gt; for all but the last snapshot in a Playwright test, so a single failure doesn’t block the rest from updating.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each rule has incorrect/correct examples, options, and rationale in the &lt;a href="https://eslint-plugin.readthedocs.io/en/latest/category/rules/"&gt;rules reference&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="getting-started-in-60-seconds"&gt;Getting started in 60 seconds&lt;/h2&gt;
&lt;p&gt;Install the plugin:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;npm&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--save-dev&lt;span class="w"&gt; &lt;/span&gt;@jupyter/eslint-plugin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Add it to your ESLint config and turn on the recommended ruleset:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jupyterPlugin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;@jupyter/eslint-plugin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;jupyter&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jupyterPlugin&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;jupyterPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recommended&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That’s it. Run &lt;code&gt;jlpm eslint&lt;/code&gt; and you’ll start seeing Jupyter-specific issues alongside your existing lint output. If you’re still on legacy &lt;code&gt;.eslintrc&lt;/code&gt; configs, there’s a &lt;a href="https://eslint-plugin.readthedocs.io/en/latest/user-guide/#for-legacy-eslintrc-configs"&gt;&lt;code&gt;recommended-legacy&lt;/code&gt;&lt;/a&gt; config too. However, we strongly recommend upgrading to &lt;a href="https://eslint.org/docs/latest/use/migrate-to-9.0.0"&gt;ESLint v9&lt;/a&gt; and using the new &lt;a href="https://eslint.org/blog/2022/08/new-config-system-part-2/"&gt;flat config&lt;/a&gt; format.&lt;/p&gt;
&lt;p&gt;Full setup details, including type-aware configuration for &lt;code&gt;plugin-activation-args&lt;/code&gt; and the JSON parser setup for &lt;code&gt;no-schema-enum&lt;/code&gt;, are in the &lt;a href="https://eslint-plugin.readthedocs.io/en/latest/user-guide/"&gt;user guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you maintain an extension, &lt;strong&gt;the most useful thing you can do today is install it and run it once on your codebase.&lt;/strong&gt; Even if you don’t adopt it permanently, the first run is often informative most extensions surface a handful of issues they didn’t know they had.&lt;/p&gt;
&lt;h2 id="help-us-encode-more-knowledge"&gt;Help us encode more knowledge&lt;/h2&gt;
&lt;p&gt;Eight rules is a starting point. The plugin is most valuable when it captures the recurring mistakes that maintainers across the ecosystem keep running into and a lot of that knowledge isn’t written down anywhere.&lt;/p&gt;
&lt;p&gt;If you’ve ever been bitten by a Jupyter-specific issue that &lt;em&gt;could&lt;/em&gt; have been a lint rule, something where you found yourself thinking “I wish my editor had warned me about this” please &lt;a href="https://github.com/jupyterlab/eslint-plugin/issues"&gt;open an issue on the repository&lt;/a&gt;. Even a short description of the bug and a minimal example helps. We’d much rather encode that knowledge into an automated check once than have every new contributor learn it the hard way.&lt;/p&gt;
&lt;div class="main-content-end" aria-hidden="true"&gt;&lt;/div&gt;
&lt;h2 id="acknowledgement"&gt;Acknowledgement&lt;/h2&gt;
&lt;p&gt;This work was funded under the &lt;a href="https://blog.jupyter.org/posts/2025/announcing-our-first-jupyter-community-funded-proposals/"&gt;first round of Jupyter Community Funded Proposals&lt;/a&gt;. We want to thank the &lt;a href="https://jupyterfoundation.org/"&gt;Jupyter Foundation&lt;/a&gt; and everyone involved in setting up this funding mechanism.&lt;/p&gt;
&lt;p&gt;We’re also grateful to &lt;a href="https://github.com/jtpio"&gt;Jeremy Tuloup&lt;/a&gt; and &lt;a href="https://github.com/brichet"&gt;Nicolas Brichet&lt;/a&gt; for their help in adopting the plugin.&lt;/p&gt;
&lt;h2 id="about-the-developers"&gt;About the developers&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/darshan-poudel8/"&gt;&lt;strong&gt;Darshan Paudyal&lt;/strong&gt;&lt;/a&gt; is an intern at &lt;a href="https://openteams.com/"&gt;OpenTeams&lt;/a&gt; and a member of the &lt;a href="https://jupyterlab-team-compass.readthedocs.io/en/latest/team.html"&gt;Jupyter Frontend Council&lt;/a&gt;. Darshan led the development of v1 of &lt;code&gt;@jupyter/eslint-plugin&lt;/code&gt;, designing and implementing the rule set, the documentation, and the release.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/michal-krassowski/"&gt;&lt;strong&gt;Michał Krassowski&lt;/strong&gt;&lt;/a&gt; is a Senior Software Engineer at &lt;a href="https://openteams.com/"&gt;OpenTeams&lt;/a&gt;. Mike provided guidance and direction for the development of the plugin.&lt;/p&gt;
&lt;h2 id="useful-links"&gt;Useful links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📦 &lt;a href="https://www.npmjs.com/package/@jupyter/eslint-plugin"&gt;npm package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;📖 &lt;a href="https://eslint-plugin.readthedocs.io/en/latest/"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;🐙 &lt;a href="https://github.com/jupyterlab/eslint-plugin"&gt;GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;💬 &lt;a href="https://github.com/jupyterlab/eslint-plugin/issues"&gt;Open an issue&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content><category term="extensions"/><category term="JavaScript"/><category term="JupyterLab"/></entry><entry><title>A User’s Journey with Plugin Playground: From First Idea to Installable JupyterLab Extension</title><link href="https://blog.jupyter.org/posts/2026/a-users-journey-with-plugin-playground-from-first-idea/" rel="alternate"/><published>2026-05-28T17:45:00+00:00</published><updated>2026-05-28T18:07:00+00:00</updated><author><name>Anuj Singh</name></author><id>tag:blog.jupyter.org,2026-05-28:/posts/2026/a-users-journey-with-plugin-playground-from-first-idea/</id><summary type="html">&lt;p&gt;We are excited to announce the 1.0 release of JupyterLab Plugin Playground, allowing you to seamlessly experiment with the creation of Jupyter Notebook and JupyterLab plugins to add any functionality you may desire.&lt;/p&gt;</summary><content type="html">&lt;p&gt;We are excited to announce the 1.0 release of JupyterLab Plugin Playground, allowing you to seamlessly experiment with the creation of Jupyter Notebook and JupyterLab plugins to add any functionality you may desire. To install:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install jupyterlab-plugin-playground
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;or use it from Binder or JupyterLite without installing anything by clicking one of these links: &lt;a href="https://mybinder.org/v2/gh/jupyterlab/plugin-playground/main?urlpath=lab"&gt;Binder (Lab)&lt;/a&gt;, &lt;a href="https://mybinder.org/v2/gh/jupyterlab/plugin-playground/main?urlpath=tree"&gt;Binder (Notebook v7)&lt;/a&gt;, &lt;a href="https://jupyterlab-plugin-playground.readthedocs.io/en/latest/lite/lab/"&gt;JupyterLite (Lab)&lt;/a&gt;, &lt;a href="https://jupyterlab-plugin-playground.readthedocs.io/en/latest/lite/tree/"&gt;JupyterLite (Notebook v7)&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;
&lt;p&gt;Building a JupyterLab extension has traditionally required setting up a complex local environment: installing dependencies from disjoint ecosystems (Node.js and Python), configuring tooling, and hunting down the right documentation. The goal of the Playground is to eliminate that setup overhead and streamline extension development. By bringing the runtime, documentation, and the context required for extension development into a single browser tab, you can go from idea to working extension without any local setup required.&lt;/p&gt;
&lt;h2 id="journey-at-a-glance"&gt;Journey At a Glance&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Start with a tiny extension idea that is easy to verify visually.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Choose a build path: &lt;strong&gt;Manual&lt;/strong&gt; or &lt;strong&gt;AI-assisted&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Load&lt;/strong&gt; and iterate inside JupyterLab until behavior is stable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build a second extension to prove the workflow is repeatable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Share plugin files/packages for product and engineering review.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Export as a &lt;strong&gt;wheel(.whl)&lt;/strong&gt; and validate installation in a clean Binder runtime.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Export as a &lt;strong&gt;zip&lt;/strong&gt; and move the scaffold into a normal GitHub repository.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="10-pick-your-starting-path"&gt;1.0: Pick Your Starting Path&lt;/h2&gt;
&lt;p&gt;At the beginning, choose the path that matches your confidence level and speed needs. If this is your first time with Plugin Playground, you can optionally run &lt;code&gt;Take the Tour&lt;/code&gt; from the Launcher or Command Palette for quick orientation before starting.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Start from File&lt;/code&gt; If you already know the APIs you need and want direct control.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Build with AI&lt;/code&gt; If you know the intended behavior but want a first draft quickly.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Both paths converge to the same shipping workflow later.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2026/a-users-journey-with-plugin-playground-from-first-idea/images/001-1_ejDftHO8TiktbPMim-EVqA.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="20-build-extension-1-manually"&gt;2.0: Build Extension #1 Manually&lt;/h2&gt;
&lt;p&gt;For the first pass, we will create something small and observable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Our Goal&lt;/strong&gt;: add a command named &lt;code&gt;Toggle Right Sidebar&lt;/code&gt; to the command palette.&lt;/p&gt;
&lt;h3 id="21-create-the-plugin-file"&gt;2.1: Create the plugin file&lt;/h3&gt;
&lt;p&gt;Launch &lt;strong&gt;Start from File&lt;/strong&gt;. This comes with a dummy template.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;JupyterFrontEnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;JupyterFrontEndPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;@jupyterlab/application&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;JupyterFrontEndPlugin&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="ow"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hello-world:plugin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;autoStart&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;activate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;JupyterFrontEnd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="22-fill-the-missing-pieces-using-the-right-sidebar"&gt;2.2: Fill the missing pieces using the right sidebar&lt;/h3&gt;
&lt;p&gt;Now build the plugin in order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;In &lt;code&gt;Tokens&lt;/code&gt; section in the right sidebar, search &lt;code&gt;ICommandPalette&lt;/code&gt; and click insert ( + icon ), so Plugin Playground adds token import and dependency wiring.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a simple command skeleton inside the &lt;strong&gt;activate&lt;/strong&gt; function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commandID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my-first-playground-plugin:toggle-sidebar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commandID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Toggle Right Sidebar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// place cursor here&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In &lt;code&gt;Commands&lt;/code&gt;, search &lt;code&gt;application:toggle-right-area&lt;/code&gt;, place your cursor inside &lt;code&gt;execute&lt;/code&gt; of the added command ( see above ) and use &lt;code&gt;Insert in selection&lt;/code&gt; This adds at the cursor position:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;application:toggle-right-area&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Register the command in the palette:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;commandPalette&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;commandID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;AAA&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After those edits, your file should look like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ICommandPalette&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;@jupyterlab/apputils&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;JupyterFrontEnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;JupyterFrontEndPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;@jupyterlab/application&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;JupyterFrontEndPlugin&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="ow"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hello-world:plugin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;autoStart&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;requires&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ICommandPalette&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;activate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;JupyterFrontEnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commandPalette&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ICommandPalette&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commandID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my-first-playground-plugin:toggle-sidebar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commandID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Toggle Right Sidebar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;application:toggle-right-area&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;commandPalette&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;commandID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;AAA&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Uses the typed plugin template (&lt;code&gt;JupyterFrontEndPlugin&amp;lt;void&amp;gt;&lt;/code&gt;) and typed &lt;code&gt;activate&lt;/code&gt; parameters. Imports &lt;code&gt;ICommandPalette&lt;/code&gt; so the plugin can add entries to the Command Palette.&lt;/li&gt;
&lt;li&gt;Registers a new command (&lt;code&gt;my-first-playground-plugin:toggle-sidebar&lt;/code&gt;) and executes &lt;code&gt;application:toggle-right-area&lt;/code&gt; when your command runs.&lt;/li&gt;
&lt;li&gt;Adds your command to the palette under the category &lt;code&gt;AAA&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="23-load-it-and-verify-behavior"&gt;2.3: Load it and verify behavior&lt;/h3&gt;
&lt;p&gt;Click &lt;strong&gt;Load Current File As Extension or click the run button in the toolbar&lt;/strong&gt;, and open Command Palette.&lt;/p&gt;
&lt;p&gt;When you run &lt;strong&gt;Load Current File As Extension&lt;/strong&gt;, Plugin Playground compiles the active file and registers the plugin object into the live JupyterLab session. During iteration, if a plugin with the same &lt;strong&gt;id&lt;/strong&gt; already exists, Plugin Playground attempts to deactivate and replace it so reload loops stay fast.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Toggle Right Sidebar&lt;/strong&gt; appears in the command palette ( open command palette by going through &lt;strong&gt;view -&amp;gt; Activate command palette&lt;/strong&gt; or Press &lt;strong&gt;Accel&lt;/strong&gt; + &lt;strong&gt;shift&lt;/strong&gt; + &lt;strong&gt;C&lt;/strong&gt;).&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2026/a-users-journey-with-plugin-playground-from-first-idea/images/002-1_RXMnfrUXeu1q7AkukZEF_A.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Running it hides/shows the JupyterLab right sidebar.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="30-build-with-ai"&gt;3.0: Build with AI&lt;/h2&gt;
&lt;p&gt;If you want a faster draft, use AI assistance for the same extension workflow.&lt;/p&gt;
&lt;p&gt;Goal: add the &lt;strong&gt;Show Active Notebook Cell Count&lt;/strong&gt; command with a friendly no-notebook fallback.&lt;/p&gt;
&lt;h3 id="31-give-ai-a-precise-request"&gt;3.1: Give AI a precise request&lt;/h3&gt;
&lt;p&gt;Use &lt;strong&gt;Build with AI&lt;/strong&gt; and give a prompt:&lt;/p&gt;
&lt;p&gt;for example, something like:-&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Create a JupyterLab plugin named &amp;quot;active-notebook-cell-counter&amp;quot;.
Add a command &amp;quot;Show Active Notebook Cell Count&amp;quot;.
If no notebook is active, show a friendly dialog message.
If a notebook is active, show the current cell count in a dialog.
Register the command in the command palette under category &amp;quot;Playground Demo&amp;quot;.
Export default plugin object.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;What this block does:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This gives AI a concrete plugin goal, command name, and fallback behavior requirements.&lt;/p&gt;
&lt;h3 id="32-use-ai-deterministic-inserts-together"&gt;3.2: Use AI + deterministic inserts together&lt;/h3&gt;
&lt;p&gt;In the &lt;strong&gt;Commands&lt;/strong&gt; tab:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Insert in selection&lt;/strong&gt; for predictable boilerplate insertion.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Prompt AI to insert&lt;/strong&gt; when placement context is tricky.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;{n}&lt;/strong&gt; when you need argument shape clarity ( Inspect the command signature before insertion: it shows expected argument names/types and return details).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Use &lt;strong&gt;Insert in selection&lt;/strong&gt; for predictable snippets, and switch to AI when insertion location or code adaptation depends on the surrounding context.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2026/a-users-journey-with-plugin-playground-from-first-idea/images/003-1_HAs9F-B9l77KolI3ZZTH3g.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;h2 id="40-iterate-faster-before-handoff"&gt;4.0: Iterate Faster Before Handoff&lt;/h2&gt;
&lt;p&gt;Once the extensions run, enable &lt;strong&gt;Run on save&lt;/strong&gt;(from the toolbar or from the settings) while polishing labels, command categories, and small behaviour details.&lt;/p&gt;
&lt;p&gt;With &lt;strong&gt;Run on save&lt;/strong&gt;, each file save triggers a reload loop for that plugin file, which makes label tweaks, command grouping changes, and message-copy iteration much faster than manual reload cycles. And with enabling the global setting (&lt;strong&gt;Load as extension on save&lt;/strong&gt;), this behaviour is present for all files by default.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2026/a-users-journey-with-plugin-playground-from-first-idea/images/004-1_wfxS4HhzRfU5-bbKYfPonA.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;h2 id="50-share-for-review"&gt;5.0: Share for Review&lt;/h2&gt;
&lt;p&gt;Before packaging, you can ask a teammate to review behaviour or just share links for the extension for a quick look using:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Share a single file&lt;/strong&gt; when sharing is about one plugin file.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Share a package&lt;/strong&gt; when your logic spans several files.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This keeps review lightweight: collaborators can inspect and discuss behaviour before you commit to repository structure, CI, and release wiring.&lt;/p&gt;
&lt;h2 id="60-export-as-wheel-and-validate-in-binder"&gt;6.0: Export as Wheel and Validate in Binder&lt;/h2&gt;
&lt;p&gt;Now test as an installable artifact in a separate environment.&lt;/p&gt;
&lt;h3 id="61-export-whl"&gt;6.1: Export &lt;code&gt;.whl&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;From the export dropdown ( in toolbar ), choose &lt;strong&gt;Export as Python package (.whl)&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="62-install-in-binder"&gt;6.2: Install in Binder&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Open JupyterLab on Binder.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Upload the downloaded wheel.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open a terminal and run:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--force-reinstall&lt;span class="w"&gt; &lt;/span&gt;./your_exported_package.whl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;What this block does:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Installs the wheel you just exported from Plugin Playground into Binder. (Uses &lt;code&gt;--force-reinstall&lt;/code&gt; so Binder replaces any previously installed copy with your newest build). Then refresh JupyterLab and verify commands.&lt;/p&gt;
&lt;h2 id="70-export-as-zip-and-create-a-github-repository"&gt;7.0: Export as Zip and Create a GitHub Repository&lt;/h2&gt;
&lt;p&gt;After runtime validation, move to normal engineering workflows.&lt;/p&gt;
&lt;h3 id="71-export-zip"&gt;7.1: Export &lt;code&gt;.zip&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Choose &lt;strong&gt;Export as archive (.zip)&lt;/strong&gt; from the toolbar export dropdown.&lt;/p&gt;
&lt;h3 id="72-bootstrap-repository"&gt;7.2: Bootstrap repository&lt;/h3&gt;
&lt;p&gt;Unzip locally, create an empty GitHub repo, then run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;init
git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.
git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Initial extension scaffold from Plugin Playground&amp;quot;&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;-M&lt;span class="w"&gt; &lt;/span&gt;main
git&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;origin&lt;span class="w"&gt; &lt;/span&gt;https://github.com/&amp;lt;your-org-or-user&amp;gt;/my-jlab-extension.git
git&lt;span class="w"&gt; &lt;/span&gt;push&lt;span class="w"&gt; &lt;/span&gt;-u&lt;span class="w"&gt; &lt;/span&gt;origin&lt;span class="w"&gt; &lt;/span&gt;main
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;What this block does:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Initializes a new Git repository from the exported scaffold and creates an initial commit that captures the generated extension baseline.&lt;/p&gt;
&lt;p&gt;Now your prototype is in a standard repo flow with PRs, CI, and release automation.&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What’s Next&lt;/h2&gt;
&lt;p&gt;Plugin Playground is still evolving. We’re actively working on the &lt;a href="https://github.com/jupyterlab/plugin-playground/pull/225"&gt;LSP integration&lt;/a&gt; for JupyterLite to bring code intelligence autocomplete and diagnostics directly into the editor, an &lt;a href="https://github.com/jupyterlab/plugin-playground/pull/231"&gt;“Ask AI” button on log errors&lt;/a&gt; that lets you send error context directly into the AI chat for instant debugging help. In the future, we would like to explore &lt;a href="https://github.com/jupyterlab/plugin-playground/issues/139"&gt;git integration&lt;/a&gt; to snapshots every successful load, paired with a &lt;a href="https://github.com/jupyterlab/plugin-playground/issues/140"&gt;built-in diff viewer&lt;/a&gt; to compare any snapshot against your current file. If you have any suggestions or encounter any problems, please let us know by opening &lt;a href="https://github.com/jupyterlab/plugin-playground/issues"&gt;an issue on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;div class="main-content-end" aria-hidden="true"&gt;&lt;/div&gt;
&lt;h2 id="acknowledgements"&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;We are grateful to the &lt;a href="https://jupyterfoundation.org/join/"&gt;Jupyter Foundation&lt;/a&gt; and its &lt;a href="https://jupyterfoundation.org/members/"&gt;members&lt;/a&gt; for sponsoring the development of Plugin Playground as part of &lt;a href="https://jupyterfoundation.org/community-funding-proposals/"&gt;Community Funding Proposals 2025&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We thank Jeremy Tuloup, Nick Bollweg, and Nicolas Brichet for providing inspiration, review, and advice across our work on the plugin and its dependencies. Finally, this work would not be possible without the &lt;a href="https://github.com/jupyterlab/plugin-playground/graphs/contributors?from=01%2F01%2F2020&amp;amp;to=18%2F12%2F2024"&gt;authors of earlier&lt;/a&gt; iterations of Plugin Playground.&lt;/p&gt;
&lt;h2 id="about-the-developers"&gt;About the Developers&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/anuj-singh-2b2ab6206/"&gt;&lt;strong&gt;Anuj Singh&lt;/strong&gt;&lt;/a&gt; is an OSS intern at OpenTeams. Anuj implemented Plugin Playground v1.0.0 and contributed to its dependencies during his internship at &lt;a href="https://openteams.com/"&gt;OpenTeams&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/smera-goel/"&gt;&lt;strong&gt;Smera Goel&lt;/strong&gt;&lt;/a&gt; is a UI/UX designer at &lt;a href="https://quansight.com/"&gt;Quansight&lt;/a&gt;. Smera contributed to the user experience and design direction of Plugin Playground.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/michal-krassowski/"&gt;&lt;strong&gt;Michał Krassowski&lt;/strong&gt;&lt;/a&gt; is a Senior Software Engineer at &lt;a href="https://openteams.com/"&gt;OpenTeams&lt;/a&gt;. Mike provided guidance and direction for the development of the Plugin Playground.&lt;/p&gt;
&lt;h2 id="further-reading"&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Blog post on &lt;a href="https://openteams.com/plugin-playground-ai-integration/"&gt;how and why we integrated AI functions in the playground&lt;/a&gt; extension&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jupyterlab/plugin-playground/blob/main/CHANGELOG.md"&gt;Changelog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jupyterlab/plugin-playground/blob/main/README.md"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content><category term="AI"/><category term="extensions"/><category term="JupyterLab"/><category term="JupyterLite"/></entry><entry><title>700 JupyterLab 4 Extensions!</title><link href="https://blog.jupyter.org/posts/2026/700-jupyterlab-4-extensions/" rel="alternate"/><published>2026-03-13T17:18:00+00:00</published><updated>2026-03-13T17:18:00+00:00</updated><author><name>Konstantin Taletskiy</name></author><id>tag:blog.jupyter.org,2026-03-13:/posts/2026/700-jupyterlab-4-extensions/</id><summary type="html">&lt;p&gt;The JupyterLab extension ecosystem just crossed 700 extensions compatible with JupyterLab 4!&lt;/p&gt;</summary><content type="html">&lt;figure&gt;
&lt;img alt="The number 700 formed by a mosaic of JupyterLab extension icons and author avatars" src="https://blog.jupyter.org/posts/2026/700-jupyterlab-4-extensions/images/001-1_z7SyUie14-caE28XRvtH9A.jpg" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;700 extensions for JupyterLab 4, and counting!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The JupyterLab extension ecosystem just crossed &lt;strong&gt;700 extensions compatible with JupyterLab 4!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That’s 700 community-built plugins — from astronomical data viewers to reactive notebooks, from genome browsers to workflow managers — created by hundreds of developers, research labs, and companies around the world.&lt;/p&gt;
&lt;h2 id="what-are-jupyterlab-extensions"&gt;What Are JupyterLab Extensions?&lt;/h2&gt;
&lt;p&gt;Extensions are how JupyterLab becomes a Git client, a dashboard builder, a genomics viewer, or an AI workspace — without changing the core application. Install one with &lt;code&gt;pip install&lt;/code&gt;, and it activates automatically.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Collage of screenshots showing popular JupyterLab extensions in action: jupytext with file format options, jupyterlab-h5web visualizing HDF5 data, jupyter-collaboration for real-time editing, jupyterlab-git with diff and staging views, ipywidgets with interactive parameter sliders, ipympl for inline matplotlib plots, jupytergis-lab for geospatial data, sidecar for side-panel output, and jupyterlab-latex for document preview." src="https://blog.jupyter.org/posts/2026/700-jupyterlab-4-extensions/images/002-1_TYJMNY9zzWcawsQsOkPuLQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Popular JupyterLab extensions&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This is by design: JupyterLab itself is built as a collection of extensions — the &lt;a href="https://github.com/jupyterlab/jupyterlab/tree/main/packages/filebrowser"&gt;file browser&lt;/a&gt;, the &lt;a href="https://github.com/jupyterlab/jupyterlab/tree/main/packages/notebook"&gt;notebook editor&lt;/a&gt;, the &lt;a href="https://github.com/jupyterlab/jupyterlab/tree/main/packages/terminal"&gt;terminal&lt;/a&gt; are all plugins. The same architecture that powers the core lets the community build what they need. For background, see &lt;a href="https://blog.jupyter.org/posts/2019/99-ways-to-extend-the-jupyter-ecosystem/"&gt;99 ways to extend the Jupyter ecosystem&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="the-ecosystem-at-700"&gt;The Ecosystem at 700&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;700+ extensions compatible with JupyterLab 4&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~960 total extensions&lt;/strong&gt; published on PyPI&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~9.8 million downloads/month&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;100M+ total downloads&lt;/strong&gt; in the past year&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By any measure, a substantial software layer has grown around JupyterLab.&lt;/p&gt;
&lt;h2 id="how-we-got-here"&gt;How We Got Here&lt;/h2&gt;
&lt;p&gt;The ecosystem crossed &lt;strong&gt;600 JL4-compatible extensions in late October 2025&lt;/strong&gt;, days before &lt;a href="https://www.jupytercon.com/"&gt;JupyterCon in San Diego&lt;/a&gt;. At the conference, we ran a full-day &lt;a href="https://jupytercon.github.io/jupytercon2025-developingextensions/"&gt;Extension Development for Everyone&lt;/a&gt; tutorial with hands-on rapid prototyping. By early March 2026, we hit &lt;strong&gt;700&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The ecosystem has been growing at a steady pace, averaging about 18 new extensions per month, with November 2025 setting an all-time monthly record of 33. Modern tooling is helping: better templates, documentation, and code generation tools have lowered the bar for what once required deep familiarity with TypeScript, Lumino, and JupyterLab internals.&lt;/p&gt;
&lt;h2 id="where-the-extensions-are"&gt;Where the Extensions Are&lt;/h2&gt;
&lt;figure&gt;
&lt;img alt="Bar chart showing number of JupyterLab extensions by category. Development and Version Control leads with 267, followed by Cloud and Platform Integration (127), AI and Code Assistance (88), Specialized Computing (83), Visualization and Dashboards (80), Educational and Grading (70), Theme (67), System and Resource Management (65), Runtime and Kernel Extensions (44), Workflow and Automation (37), and Data and Database Integration (26)" src="https://blog.jupyter.org/posts/2026/700-jupyterlab-4-extensions/images/003-1_lijkfmv6G5n96sO2vRzB5Q.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Number of JupyterLab extensions by category&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img alt="Bar chart showing 30-day PyPI downloads per category. Development and Version Control leads at 5.4 million, followed by Visualization and Dashboards (2.7M), System and Resource Management (602K), AI and Code Assistance (253K), Runtime and Kernel Extensions (206K), Cloud and Platform Integration (178K), Data and Database Integration (148K), Educational and Grading (99K), Workflow and Automation (69K), Specialized Computing (56K), and Theme (48K)" src="https://blog.jupyter.org/posts/2026/700-jupyterlab-4-extensions/images/004-1_wW76DvGMkq2l_Xvr0JO_vw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Monthly PyPI downloads by category&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Development &amp;amp; Version Control dominates both in count (267) and downloads (5.4M/month). Visualization &amp;amp; Dashboards (2.7M/month) and System &amp;amp; Resource Management (602K/month) round out the top three most downloaded categories. But the fastest-growing categories point to where things are heading. Here’s what’s new in 2026:&lt;/p&gt;
&lt;h2 id="jupyterlabs-ai-layer-starts-taking-shape"&gt;JupyterLab’s AI Layer Starts Taking Shape&lt;/h2&gt;
&lt;p&gt;AI isn’t yet the biggest category in JupyterLab, but it may be the clearest signal of where new interaction patterns are emerging:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/jupyter-ai-acp-client"&gt;&lt;strong&gt;jupyter-ai-acp-client&lt;/strong&gt;&lt;/a&gt; — Brings external AI agents into JupyterLab’s chat via the Agent Communication Protocol. Ships with Claude Code and Kiro personas.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/nb-margin"&gt;&lt;strong&gt;nb-margin&lt;/strong&gt;&lt;/a&gt; — Annotate cells with margin comments, and Claude Code edits them. A different paradigm from chat-based AI.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/jupyterlite-ai-kernels"&gt;&lt;strong&gt;jupyterlite-ai-kernels&lt;/strong&gt;&lt;/a&gt; — AI-powered kernels for JupyterLite, from Jeremy Tuloup. AI-assisted computation entirely in the browser.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/jupyter-chat-components"&gt;&lt;strong&gt;jupyter-chat-components&lt;/strong&gt;&lt;/a&gt; — Reusable chat UI components from Project Jupyter — building blocks for the next generation of AI tools.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These extensions reflect what the JupyterLab team identified as a 2026 priority: first-class integration with AI tooling.&lt;/p&gt;
&lt;h2 id="reproducibility-gets-a-toolchain"&gt;Reproducibility Gets a Toolchain&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://labextensions.dev/extensions/calkit-python"&gt;&lt;strong&gt;calkit-python&lt;/strong&gt;&lt;/a&gt; is the most downloaded new extension of 2026 (11,000+ monthly downloads). It gives notebooks project-scoped environments, graphical package management via Astral’s &lt;code&gt;uv&lt;/code&gt;, and one-click notebook pipelines with freshness tracking. Think “Makefiles for notebooks” meets “Poetry for Jupyter.”&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Calkit extension in JupyterLab showing a notebook pipeline with three stages — collect-data, process-data, and plot-results — in the left sidebar, alongside a Python notebook with pandas code. The toolbar shows environment and pipeline stage indicators" src="https://blog.jupyter.org/posts/2026/700-jupyterlab-4-extensions/images/005-0_vKMU4QiLD406Ac9c.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Calkit manages notebook pipelines with environment tracking and one-click reruns. The orange ‘run’ button signals stale outputs that need to be regenerated.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href="https://labextensions.dev/extensions/jupyter-projspec"&gt;&lt;strong&gt;jupyter-projspec&lt;/strong&gt;&lt;/a&gt; (from the fsspec contributors) takes a complementary approach — it brings &lt;a href="https://github.com/fsspec/projspec"&gt;projspec&lt;/a&gt; into JupyterLab, letting you scan and analyze project structures directly from the notebook environment.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="jupyter-projspec extension in JupyterLab showing the Project Spec sidebar panel with detected project types including Git Repository, Pixi, and Poetry, alongside the file browser and JupyterLab launcher with notebook and console options." src="https://blog.jupyter.org/posts/2026/700-jupyterlab-4-extensions/images/006-0_GSE92ExuZWmjQHtA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;jupyter-projspec integrates to system filebrowser to show the project metadata&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="science"&gt;Science&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/fitsview"&gt;&lt;strong&gt;fitsview&lt;/strong&gt;&lt;/a&gt; — Stream FITS astronomical data slices directly in JupyterLab without downloading full files.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/jupyterlab-urdf-test"&gt;&lt;strong&gt;jupyterlab-urdf-test&lt;/strong&gt;&lt;/a&gt; — 3D robot model viewer/editor (URDF + Three.js), from &lt;a href="https://github.com/jupyter-robotics"&gt;jupyter-robotics&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/climb-jupyter-igv"&gt;&lt;strong&gt;climb-jupyter-igv&lt;/strong&gt;&lt;/a&gt;— Integrative Genomics Viewer with S3 access for bioinformatics.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/ggblab"&gt;&lt;strong&gt;ggblab&lt;/strong&gt;&lt;/a&gt;— GeoGebra interactive geometry with bidirectional Python communication. Second most downloaded new extension of 2026.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="accessibility"&gt;Accessibility&lt;/h2&gt;
&lt;p&gt;Accessibility has been a growing focus for JupyterLab core and extensions are starting to address it too:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/jupyterlab-a11y-checker"&gt;&lt;strong&gt;jupyterlab-a11y-checker&lt;/strong&gt;&lt;/a&gt;— From UC Berkeley’s &lt;a href="https://github.com/berkeley-dsep-infra/jupyterlab-a11y-checker"&gt;DSEP infrastructure team&lt;/a&gt;, this extension scans notebooks for WCAG 2.1 AA issues: missing alt text, heading structure, table headers, color contrast, and link text. Guided fix interfaces, optional AI suggestions, and a CLI for CI pipelines. Over 11,000 total downloads and a &lt;a href="https://a11y-checker-guide.datahub.berkeley.edu/"&gt;documentation site&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labextensions.dev/extensions/jupyterlab-change-ui-font-size-fix"&gt;&lt;strong&gt;jupyterlab-change-ui-font-size-fix&lt;/strong&gt;&lt;/a&gt; — Fixes file browser icon misalignment when users change the UI font size — a small but real pain point for anyone who needs larger text.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="27-extensions-one-platform"&gt;27 Extensions, One Platform&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/stellarshenson/stellars-jupyterlab-ds"&gt;Stellars&lt;/a&gt; is a JupyterLab-based data science platform — GPU support, MLflow, TensorBoard, Optuna — assembled from &lt;strong&gt;27 custom extensions&lt;/strong&gt; covering everything from &lt;a href="https://labextensions.dev/extensions/jupyterlab-branding-extension"&gt;branding&lt;/a&gt; and &lt;a href="https://labextensions.dev/extensions/jupyterlab-vscode-icons-extension"&gt;file icons&lt;/a&gt; to &lt;a href="https://labextensions.dev/extensions/jupyterlab-kernel-terminal-workspace-culler-extension"&gt;kernel management&lt;/a&gt; and &lt;a href="https://labextensions.dev/extensions/jupyterlab-drawio-render-extension"&gt;diagram rendering&lt;/a&gt;, &lt;a href="https://labextensions.dev/extensions/jupyterlab-vscode-icons-extension"&gt;VS Code file icons&lt;/a&gt;, &lt;a href="https://labextensions.dev/extensions/jupyterlab-trash-mgmt-extension"&gt;trash management&lt;/a&gt;, &lt;a href="https://labextensions.dev/extensions/jupyterlab-mmd-to-png-extension"&gt;Mermaid-to-PNG conversion&lt;/a&gt;, and more.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;JupyterLab is now flexible enough that one developer can assemble a domain-specific product entirely from extension building blocks.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="want-to-build-your-own"&gt;Want to Build Your Own?&lt;/h2&gt;
&lt;p&gt;The JupyterCon tutorial is fully available: &lt;a href="https://jupytercon.github.io/jupytercon2025-developingextensions/"&gt;step-by-step materials&lt;/a&gt; and the complete &lt;a href="https://www.youtube.com/watch?v=z-KZ6CjZjbM"&gt;YouTube recording&lt;/a&gt;. It covers scaffolding, plugin architecture, publishing to PyPI, and rapid prototyping techniques. The tools have never been more accessible.&lt;/p&gt;
&lt;h2 id="how-we-track-this"&gt;How We Track This&lt;/h2&gt;
&lt;p&gt;The data behind this post comes from the &lt;a href="https://labextensions.dev"&gt;JupyterLab Marketplace&lt;/a&gt;, a community &lt;a href="https://github.com/orbrx/jupyter-marketplace"&gt;project&lt;/a&gt; that tracks all published JupyterLab extensions using PyPI data. The marketplace refreshes automatically and provides download trends, category breakdowns, and discovery tools.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="JupyterLab Marketplace homepage showing a grid of popular extensions with download counts and GitHub stars — including jupyter-archive, jupyter-resource-usage, ipyanchorviz, jupyterlab-execute, jupyter-collaboration, jupysql-plugin, jupyterlab-unfold, jupyter-ai, jupyterlab-code-snippets, and jupyterlab-autoscroll." src="https://blog.jupyter.org/posts/2026/700-jupyterlab-4-extensions/images/007-1_CkZpwkmEKNiXm_XsiBRACQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;JupyterLab Marketplace&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;For more on the data and methodology, see our &lt;a href="https://www.youtube.com/watch?v=OWt3Yzhrs1E"&gt;PyData Boston 2025 talk&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What’s Next&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;New interaction patterns&lt;/strong&gt; are still being figured out — chat-based assistance, cell annotations, agent protocols. Probably all of them for different use cases.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reproducibility tooling&lt;/strong&gt; suggests the community is ready for opinionated workflow management built into the notebook experience.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ensuring extensions keep working as JupyterLab evolves is critical — the team has been &lt;a href="https://github.com/jupyterlab/frontends-team-compass/issues/301"&gt;discussing extension compatibility testing&lt;/a&gt; at recent contributors calls.&lt;/p&gt;
&lt;p&gt;For the &lt;a href="https://labextensions.dev"&gt;Marketplace&lt;/a&gt; itself, we’re working on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Deeper integration with JupyterLab Extension Manager&lt;/strong&gt; — deep links and “Install in JupyterLab” buttons to go from discovery to installation in one click.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expanding Trove classifiers&lt;/strong&gt; to indicate Jupyter Notebook and JupyterLite support. All three use the same extension system, with important caveats: Notebook extensions need to target different UI elements, and JupyterLite extensions cannot have a server component.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Better contribution signals&lt;/strong&gt; — surfacing commits, PRs, and issues to help users gauge how actively maintained an extension is.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;At 700 extensions, the community now shapes JupyterLab as much as the core team does. If you’re building extensions, thank you! Every one of them makes Jupyter better for someone.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="main-content-end" aria-hidden="true"&gt;&lt;/div&gt;
&lt;h2 id="about-the-author"&gt;About the author&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/taletskiy/"&gt;Konstantin Taletskiy&lt;/a&gt; is a Senior Software Engineer at &lt;a href="https://www.anaconda.com/"&gt;Anaconda&lt;/a&gt; working on open-source Jupyter. He is a contributor to JupyterLab, maintainer of &lt;a href="https://github.com/mamba-org/mamba-gator"&gt;mamba-gator&lt;/a&gt; and &lt;a href="https://github.com/jupyterlab/jupyterlab-latex"&gt;jupyterlab-latex&lt;/a&gt;, and the creator of the &lt;a href="https://labextensions.dev"&gt;JupyterLab Marketplace&lt;/a&gt;.&lt;/p&gt;
</content><category term="community"/><category term="extensions"/><category term="JupyterLab"/></entry><entry><title>Instantly view Parquet files in JupyterLab with Arbalister</title><link href="https://blog.jupyter.org/posts/2026/instantly-view-parquet-files-in-jupyterlab-with/" rel="alternate"/><published>2026-01-29T16:18:00+00:00</published><updated>2026-02-04T14:50:00+00:00</updated><author><name>Antoine Prouvost</name></author><id>tag:blog.jupyter.org,2026-01-29:/posts/2026/instantly-view-parquet-files-in-jupyterlab-with/</id><summary type="html">&lt;p&gt;A colleague sends you a SQLite file, a Parquet dataset, or an Avro snapshot. You need to explore the content, but you’re not sure what’s inside or even how to open it. You shouldn’t have to write code, craft SQL queries, or recall the syntax for read_parquet in a Python library.&lt;/p&gt;</summary><content type="html">&lt;p&gt;A colleague sends you a SQLite file, a Parquet dataset, or an Avro snapshot.&lt;br&gt;
You need to explore the content, but you’re not sure what’s inside or even how to open it. You shouldn’t have to write code, craft SQL queries, or recall the syntax for &lt;code&gt;read_parquet&lt;/code&gt; in a Python library. Instead, you should be able to click the file once and instantly see the tables and metadata.&lt;/p&gt;
&lt;p&gt;Despite being the &lt;em&gt;de-facto&lt;/em&gt; standard for data science and exploration, JupyterLab lacks this basic capability out of the box. Of course, there are extremely well designed libraries to read, process, and transform tabular data — but opening and viewing a file with a table should be simpler. A workflow with libraries like Pandas or Polars require you to install relevant packages, create a notebook, and iterate on queries to get a full picture of the data.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;display.max_rows&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;display.max_columns&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;display.width&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/2025/PERFORMANCE_SNAPSHOT_2025_01_05.parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Whether for a newcomer to data science, or someone that needs to investigate many different files with hard to remember schemas, this is a bit cumbersome. Comma separated value files (CSV) have better support: double click on the file to see it open (see figure below). That is a great way to understand the business logic and answer questions such as &lt;em&gt;What type of information is in this file? What do values typically look like? Does it seem to contain all the data I need? What is the difference between &lt;code&gt;customer_id&lt;/code&gt; and &lt;code&gt;user_id&lt;/code&gt;?&lt;/em&gt;&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="The built-in CSV viewer of JupterLab shows data as a table." src="https://blog.jupyter.org/posts/2026/instantly-view-parquet-files-in-jupyterlab-with/images/001-1_pHSj3jmo0D69yzM86_XAZg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;The JupyterLab CSV viewer&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Arbalister&lt;/strong&gt;, our new JupyterLab extension, changes this. With Arbalister, you can &lt;strong&gt;double-click to instantly view&lt;/strong&gt; a wide range of tabular data files: &lt;strong&gt;Parquet, CSV, Avro, ORC, SQLite,&lt;/strong&gt; and more…&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Arbalister viewer can open Parquet files and more" src="https://blog.jupyter.org/posts/2026/instantly-view-parquet-files-in-jupyterlab-with/images/002-1_CHOorqcOpRmlHlHFIPpF-A.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Arbalister viewer can open Parquet files and more&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The extension will load data as it is displayed, making it possible to view it almost instantaneously even if the file does not fit in memory. A toolbar lets the user select some reading options, such as the delimiter for a CSV file, or selecting the table to display in the SQLite file!&lt;/p&gt;
&lt;h2 id="arrow-over-the-wire"&gt;Arrow over the wire&lt;/h2&gt;
&lt;p&gt;Fueling it all is the &lt;a href="https://arrow.apache.org/"&gt;Apache Arrow&lt;/a&gt; ecosystem, an in-memory format for tabular data used in most data science libraries. Plenty of files types can already be read into an Arrow data structure so we exploit it heavily in Arbalister (with &lt;a href="https://datafusion.apache.org/"&gt;Apache Datafusion&lt;/a&gt; in our case).&lt;/p&gt;
&lt;p&gt;The table displayed in the user’s browser reuses JupyterLab Lumino DataGrid with a custom data model. In the latter, we divide the whole table in chunks across both the rows and columns axes (&lt;em&gt;e.g.&lt;/em&gt; 512 rows and 24 columns). When we need to display data from a chunk that is not already available, we make an HTTP request to a server-side extension that open the file, read the relevant portion into an Arrow table and return it to the client. The table is returned as Arrow IPC (Inter Process Communication, a binary format close to the original Arrow memory) in the response body. In the client, a lightweight Arrow implementation can read it back efficiently.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="A diagram show a client server-server architecture for Arbalister, with the backend responsible for reading many different file types into an Arrow IPC format, while the frontend displays it." src="https://blog.jupyter.org/posts/2026/instantly-view-parquet-files-in-jupyterlab-with/images/003-1_6qFKCpSW5d3GT1p6EOfekg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Arbalister client-server architecture&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When nothing else is happening, the implementation will also pre-fetch some of the next chunks in the background so that scrolling past a chunk boundary is imperceptible.&lt;/p&gt;
&lt;p&gt;Because it all works in Arrow, which is standard for tabular data, adding support for new types of files that can be read into the Arrow format is extremely easy. Vortex, Lance, or whichever other format becomes relevant. But that is not all…&lt;/p&gt;
&lt;p&gt;The existing SQLite support is made possible through &lt;a href="https://arrow.apache.org/adbc/"&gt;ADBC&lt;/a&gt;, an SQL connector that reads databases into Arrow format. SQLite databases fit into single files, which fit the abstraction used in Arbalister, but ADBC could also be used to add general database exploration (Postgres &lt;em&gt;etc&lt;/em&gt;.) into Arbalister. With Datafusion, we could also add visualization for data lakehouses: partitioned datasets (&lt;em&gt;e.g.&lt;/em&gt; &lt;a href="https://delta.io/"&gt;DeltaLake&lt;/a&gt; or &lt;a href="https://iceberg.apache.org/"&gt;Apache Iceberg&lt;/a&gt;) over remote object storage (S3). With Intelligent predicate and projections pushdowns, we could visualize remote tera-byte table instantly by only downloading the relevant parts.&lt;/p&gt;
&lt;h2 id="looking-further"&gt;Looking further&lt;/h2&gt;
&lt;p&gt;There are multiple things that would make great improvements to Arbalister. We have already mentioned:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Optional support for newer file types: Vortex, Lance;&lt;/li&gt;
&lt;li&gt;Object storage and data lakehouses support;&lt;/li&gt;
&lt;li&gt;SQL databases support.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another direction is support for &lt;a href="https://jupyterlite.readthedocs.io/en/stable/"&gt;JupyterLite&lt;/a&gt;, a &lt;a href="https://webassembly.org/"&gt;WebAssembly&lt;/a&gt; distribution of JupyterLab running entirely in the browser. This setting is great for education because it does not require more than a static file server to host.&lt;br&gt;
Being able to work directly with Parquet files in the browser is great for making dashboard and interactive demonstrations. Arrow and Datafusion already have some experimental support for WebAssembly so this is not so far-fetched.&lt;/p&gt;
&lt;p&gt;Finally Arbalister could use a few features to investigate deeper questions about the data. For instance, beyond the &lt;em&gt;What does the data look like?&lt;/em&gt; question, some column filters could help answer questions such as &lt;em&gt;What does the data look like in November 2024 for John Doe?&lt;/em&gt; Perhaps even an SQL console for more advanced queries.&lt;/p&gt;
&lt;h2 id="an-extension-point"&gt;An extension point&lt;/h2&gt;
&lt;p&gt;The server extension being only a way to serve Arrow data, it can be reused by other libraries to use it in other ways. For instance, if we knew that our table contains time-series data, a second viewer could display it as a graph.&lt;br&gt;
Similarly for geospatial data.&lt;/p&gt;
&lt;p&gt;If you are interested to use or contribute to Arbalister, head to &lt;a href="https://github.com/QuantStack/Arbalister"&gt;our GitHub&lt;/a&gt;!&lt;/p&gt;
&lt;div class="main-content-end" aria-hidden="true"&gt;&lt;/div&gt;
&lt;h2 id="about-the-author"&gt;About the Author&lt;/h2&gt;
&lt;p&gt;Antoine Prouvost is a senior scientific software engineer at QuantStack. His work on Arbalister was funded by Bloomberg.&lt;/p&gt;
</content><category term="extensions"/><category term="JupyterLab"/><category term="visualization"/></entry><entry><title>Building AI Agents for JupyterLab using Notebook Intelligence</title><link href="https://blog.jupyter.org/posts/2025/building-ai-agents-for-jupyterlab-using-notebook/" rel="alternate"/><published>2025-02-24T18:03:00+00:00</published><updated>2025-02-24T18:03:00+00:00</updated><author><name>Mehmet Bektas</name></author><id>tag:blog.jupyter.org,2025-02-24:/posts/2025/building-ai-agents-for-jupyterlab-using-notebook/</id><summary type="html">&lt;p&gt;It is now possible to build AI Agents for JupyterLab and access from Copilot Chat UI, using Notebook Intelligence!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;em&gt;Please note that this is not an official Jupyter subproject but an independent open-source tool for JupyterLab users who want to use GitHub Copilot as an AI coding assistant.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/notebook-intelligence/notebook-intelligence"&gt;Notebook Intelligence&lt;/a&gt; (NBI) is an AI coding assistant and extensible AI framework for JupyterLab. (&lt;em&gt;For an introduction to NBI see&lt;/em&gt; &lt;a href="https://blog.jupyter.org/posts/2025/introducing-notebook-intelligence/"&gt;&lt;em&gt;Introducing Notebook Intelligence&lt;/em&gt;&lt;/a&gt; &lt;em&gt;and for basics of extending NBI see&lt;/em&gt; &lt;a href="https://nbi.plmbr.dev/blog/archive/building-ai-extensions-for-jupyterlab/"&gt;&lt;em&gt;Building AI Extensions for JupyterLab&lt;/em&gt;&lt;/a&gt; &lt;em&gt;blog posts.&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;GitHub Copilot and other AI coding assistants are great at generating code and answering coding related questions. But they can do a lot more than generating text and code thanks to LLM features such as tool calling and AI agents. NBI provides an extensible AI framework to integrate tool calling and AI agents into JupyterLab Copilot Chat.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="AI Agent extension example" src="https://blog.jupyter.org/posts/2025/building-ai-agents-for-jupyterlab-using-notebook/images/001-1_x9r8gIqX1wbubuE8sT6GFg.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;AI Agent extension example&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="what-is-tool-calling-and-an-ai-agent"&gt;What is tool calling and an AI Agent?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Tool calling&lt;/strong&gt; is a feature of LLMs. It lets you introduce your own functions to LLM so that they can be called in response to chat prompts. LLM can convert natural language prompts to function calls with arguments. Tool calls are executed on the client side (i.e. Jupyter server) by your extension and only the function schema is provided to the LLM. Tool calling lets LLM interact with real time data, proprietary or external apps and services.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI Agents&lt;/strong&gt; are collections of tools that can run tasks on behalf of the user. Given a natural language prompt, LLMs can reason, create an execution plan and call multiple tools in a chain. NBI provides a framework to build these type of AI Agent integrations and handles the orchestration between LLMs and your tools.&lt;/p&gt;
&lt;h2 id="ai-agent-extension-example"&gt;AI Agent Extension Example&lt;/h2&gt;
&lt;p&gt;Let’s build an AI Agent for JupyterLab using Notebook Intelligence extension APIs. (&lt;em&gt;The full source code for this extension is&lt;/em&gt; &lt;a href="https://github.com/notebook-intelligence/nbi-ai-agent-example"&gt;&lt;em&gt;available here&lt;/em&gt;&lt;/a&gt;.) This will be an AI agent for map creation and notebook sharing. It will have the following capabilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Looking up geo-coordinates for an address&lt;/li&gt;
&lt;li&gt;Showing maps centered at an address in the Copilot Chat UI&lt;/li&gt;
&lt;li&gt;Creating notebooks that show maps centered at specified addresses&lt;/li&gt;
&lt;li&gt;Sharing notebooks publicly using &lt;a href="https://notebooksharing.space/"&gt;notebooksharing.space&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The tasks above will be run by the AI Agent in response to natural language prompts by the user.&lt;/p&gt;
&lt;p&gt;For this extension we will build four tools that will be integrated into JupyterLab Copilot Chat, for each of the tasks above. Tools are defined as classes derived from NBI &lt;code&gt;Tool&lt;/code&gt; abstract class. A tool needs to implement the methods and properties defined in this base class.&lt;/p&gt;
&lt;p&gt;Tool class provides the metadata information for the tool and implements the &lt;code&gt;pre_invoke&lt;/code&gt; and &lt;code&gt;handle_tool_call&lt;/code&gt; methods. &lt;code&gt;pre_invoke&lt;/code&gt; method is called right before &lt;code&gt;handle_tool_call&lt;/code&gt; with the tool arguments and it gives an opportunity for the tool to prompt for confirmation of the tool execution.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;schema&lt;/code&gt; property of the Tool is the function schema based on OpenAI’s function calling schema. It lets you describe your function and its parameters as an object. A Tool is expected to return an object as response from the &lt;code&gt;handle_tool_call&lt;/code&gt; method call.&lt;/p&gt;
&lt;h2 id="geo-coordinates-lookup-tool"&gt;Geo Coordinates Lookup Tool&lt;/h2&gt;
&lt;p&gt;This tool looks up geo-coordinates for an address using &lt;a href="https://github.com/geopy/geopy"&gt;Nominatim&lt;/a&gt; library. &lt;code&gt;pre_invoke&lt;/code&gt; method for this tool only shows a message in Chat UI before looking up for the geo-coordinates in &lt;code&gt;handle_tool_call&lt;/code&gt; method.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;GeoCoordinateLookupTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;geo_coordinate_lookup&amp;quot;&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Get geo-coordinates from an address&amp;quot;&lt;/span&gt;
    
    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;This is a tool that converts an address to a geo-coordinates&amp;quot;&lt;/span&gt;
    
    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;function&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;function&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s2"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s2"&gt;&amp;quot;parameters&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;object&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s2"&gt;&amp;quot;properties&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="s2"&gt;&amp;quot;address&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;string&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="s2"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Address to convert to geo-coordinates&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="s2"&gt;&amp;quot;required&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;address&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="s2"&gt;&amp;quot;additionalProperties&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pre_invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;address&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Getting coordinates for &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle_tool_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;address&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geolocator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geocode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;latitude&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;longitude&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figure&gt;
&lt;img alt="Get geo-coordinates tool" src="https://blog.jupyter.org/posts/2025/building-ai-agents-for-jupyterlab-using-notebook/images/002-1_5qScMVUaGlIWWF7_F36Fsg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Get geo-coordinates tool&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="map-response-generator-tool"&gt;Map Response Generator Tool&lt;/h2&gt;
&lt;p&gt;This tool shows a map in Copilot Chat UI centered at geo-coordinates. In &lt;code&gt;pre_invoke&lt;/code&gt; method this method only shows a notification message in Chat UI. In &lt;code&gt;handle_tool_call&lt;/code&gt; method, this tool returns a &lt;code&gt;HTMLFrame&lt;/code&gt; response that uses HTML to show a map centered at the requested location using Google Maps.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;MapResponseGeneratorTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pre_invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;geo_coordinates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;geo_coordinates&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;latitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;latitude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;longitude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Showing a map centered at latitude: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; and longitude: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle_tool_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;geo_coordinates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;geo_coordinates&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;latitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;latitude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;longitude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HTMLFrameData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&amp;lt;iframe width=&amp;quot;100%&amp;quot; height=&amp;quot;100%&amp;quot; frameborder=&amp;quot;0&amp;quot; scrolling=&amp;quot;no&amp;quot; marginheight=&amp;quot;0&amp;quot; marginwidth=&amp;quot;0&amp;quot; id=&amp;quot;gmap_canvas&amp;quot; src=&amp;quot;https://maps.google.com/maps?width=400&amp;amp;amp;height=400&amp;amp;amp;hl=en&amp;amp;amp;q=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;amp;t=&amp;amp;amp;z=11&amp;amp;amp;ie=UTF8&amp;amp;amp;iwloc=B&amp;amp;amp;output=embed&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;result&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;I showed the map&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Below is an example prompt showing map centered at “Golden Gate Bridge, San Francisco”. Note that an address was provided to Copilot as the input but Map Response Generator Tool accepts only geo-coordinates as input. This is where LLM automatically decided that it needs to first call the Geo Coordinates Lookup Tool to get geo-coordinates for this address and then it called the Map Response Generator Tool with the geo-coordinates. LLM automatically chained multiple tools and NBI handled this chaining to get the correct response for the user’s prompt.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Map Response Generator Tool" src="https://blog.jupyter.org/posts/2025/building-ai-agents-for-jupyterlab-using-notebook/images/003-1_6o_XrO-44kKaGYmEefsXKQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Map Response Generator Tool&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="map-notebook-creator-tool"&gt;Map Notebook Creator Tool&lt;/h2&gt;
&lt;p&gt;This tool creates a notebook centered at the specified geo-coordinates. In &lt;code&gt;pre_invoke&lt;/code&gt; method this method only shows a notification message in Chat UI. In &lt;code&gt;handle_tool_call&lt;/code&gt; method, the tool creates a notebook using &lt;code&gt;nbformat&lt;/code&gt; library, saves it to disk and then opens the notebook in JupyterLab UI using the &lt;code&gt;response.run_ui_command&lt;/code&gt; NBI method.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;MapNotebookCreatorTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pre_invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;geo_coordinates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;geo_coordinates&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;latitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;latitude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;longitude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Creating a map notebook for latitude: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; and longitude: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle_tool_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;geo_coordinates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;geo_coordinates&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;latitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;latitude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;longitude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;map_file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;map_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%Y%m&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s1"&gt;_%H%M%S&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.ipynb&amp;quot;&lt;/span&gt;

        &lt;span class="n"&gt;nb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nbf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_notebook&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s2"&gt;        ### This map notebook was created by an AI Agent using [Notebook Intelligence](https://github.com/notebook-intelligence)&lt;/span&gt;
&lt;span class="s2"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

        &lt;span class="n"&gt;install_code_cell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%%&lt;/span&gt;&lt;span class="s2"&gt;capture&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;%pip install folium&amp;quot;&lt;/span&gt;

        &lt;span class="n"&gt;map_code_cell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s2"&gt;        import folium&lt;/span&gt;

&lt;span class="s2"&gt;        map = folium.Map(location=[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;], zoom_start=13)&lt;/span&gt;
&lt;span class="s2"&gt;        map&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

        &lt;span class="n"&gt;nb&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cells&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;nbf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_markdown_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;nbf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_code_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;install_code_cell&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;nbf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_code_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;map_code_cell&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;nb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;kernelspec&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;python3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;nbf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;map_file_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run_ui_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;docmanager:open&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;path&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;map_file_name&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;result&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;I created and opened the map notebook&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figure&gt;
&lt;img alt="Map Notebook Creator Tool" src="https://blog.jupyter.org/posts/2025/building-ai-agents-for-jupyterlab-using-notebook/images/004-1_2BZGe7aGPnNTPY79ZNhUDQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Map Notebook Creator Tool&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="notebook-share-tool"&gt;Notebook Share Tool&lt;/h2&gt;
&lt;p&gt;This tool shares a notebook publicly by uploading it to &lt;a href="https://notebooksharing.space/"&gt;notebooksharing.space&lt;/a&gt; and displays the link to the shared notebook.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;pre_invoke&lt;/code&gt; method implementation, this tool asks for confirmation first as this operation is an undoable share of the notebook publicly. Only after the user confirms, &lt;code&gt;handle_tool_call&lt;/code&gt; is executed. In &lt;code&gt;handle_tool_call&lt;/code&gt; method the tool uploads the notebook at the &lt;code&gt;notebook_file_path&lt;/code&gt; using &lt;a href="https://github.com/notebook-sharing-space/nbss-upload"&gt;nbss_upload&lt;/a&gt; library and then shows the link to the shared notebook on &lt;a href="https://notebooksharing.space/"&gt;notebooksharing.space&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;NotebookShareTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pre_invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;notebook_file_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Sharing notebook &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;confirmationTitle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Confirm sharing&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;confirmationMessage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Are you sure you want to share the notebook at &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;? This will upload the notebook to public internet and cannot be undone.&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle_tool_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;notebook_file_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;share_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nbss_upload&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upload_notebook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://notebooksharing.space&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AnchorData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;share_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Click here to view the shared notebook &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;result&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Notebook &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39; has been shared at: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;share_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figure&gt;
&lt;img alt="Notebook Share Tool" src="https://blog.jupyter.org/posts/2025/building-ai-agents-for-jupyterlab-using-notebook/images/005-1_CYXJbXzmMGnuW_c_ktwkgA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Notebook Share Tool&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="tool-call-schema-definitions"&gt;Tool call schema definitions&lt;/h2&gt;
&lt;p&gt;It is important to define schemas of the tools clearly and disambiguate the tools as much as possible so that the LLM can invoke the proper tool based on the user prompt. LLM parses the user prompt, decides which tools to call and generates the input parameters for the call.&lt;/p&gt;
&lt;p&gt;If current file or selection is made visible by the user, NBI can provide the file paths and content as context to the LLM. That way LLM can use those as additional context for a tool call. That is how Notebook Share Tool was able to access the current notebook file.&lt;/p&gt;
&lt;h2 id="tool-chaining"&gt;Tool chaining&lt;/h2&gt;
&lt;p&gt;After parsing the user prompt, LLM creates an execution plan and can call multiple tools in a chain. NBI handles this tool chaining for you. It is important to define your schemas with the chaining in mind. Consider defining matching tool outputs and inputs so that the output of a tool can be passed onto another one directly if needed.&lt;/p&gt;
&lt;p&gt;Notice that in this extension example MapResponseGeneratorTool and MapNotebookCreatorTool both take in geo_coordinates (latitude, longitude) as input and GeoCoordinateLookupTool outputs geo_coordinates. This lets LLM to directly pass the output of GeoCoordinateLookupTool to MapResponseGeneratorTool and MapNotebookCreatorTool. It also lets a user to use an address to trigger MapResponseGeneratorTool and MapNotebookCreatorTool, because LLM knows that there is another tool it can call to generate input (geo_coordinates) from address for these tools.&lt;/p&gt;
&lt;h2 id="chat-participant"&gt;Chat Participant&lt;/h2&gt;
&lt;p&gt;In NBI AI framework, AI Agents are defined as chat participants and tools are tied to specific chat participants. For our extension we create &lt;code&gt;AIAgentChatParticipant&lt;/code&gt; as our participant (for more details on NBI extensions and chat participants see &lt;a href="https://nbi.plmbr.dev/blog/archive/building-ai-extensions-for-jupyterlab/"&gt;this blog&lt;/a&gt;). Our chat participant returns list of tools it defines from the &lt;code&gt;tools&lt;/code&gt; property.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;handle_chat_request&lt;/code&gt; method our chat participant passes the request to the base &lt;code&gt;ChatParticipant&lt;/code&gt; class to handle tool calling for us.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;AIAgentChatParticipant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChatParticipant&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;ai-agent&amp;quot;&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    
    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;GeoCoordinateLookupTool&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;MapResponseGeneratorTool&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;MapNotebookCreatorTool&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;NotebookShareTool&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle_chat_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_chat_request_with_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="nbi-extension"&gt;NBI Extension&lt;/h2&gt;
&lt;p&gt;Finally we create our NBI extension class &lt;code&gt;AIAgentExtension&lt;/code&gt;. This class basically registers our chat participant to NBI on extension activation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;AIAgentExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NotebookIntelligenceExtension&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;activate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;participant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AIAgentChatParticipant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register_chat_participant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;participant&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;AI Agent example extension activated&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That is all there is to create an AI Agent for JupyterLab using Notebook Intelligence. The &lt;a href="https://github.com/notebook-intelligence/nbi-ai-agent-example"&gt;full source code&lt;/a&gt; for this example is available along with installation instructions for you to use as a reference and/or build on top.&lt;/p&gt;
&lt;h2 id="try-it-out-and-share-your-feedback"&gt;Try it out and share your feedback!&lt;/h2&gt;
&lt;p&gt;I am looking forward to seeing the AI Agents built by the community. Please try the extension APIs and share your feedback using project’s &lt;a href="https://github.com/notebook-intelligence/notebook-intelligence/issues"&gt;GitHub issues&lt;/a&gt;! User feedback from the community will shape the project’s roadmap.&lt;/p&gt;
&lt;div class="main-content-end" aria-hidden="true"&gt;&lt;/div&gt;
&lt;h2 id="about-the-author"&gt;About the Author&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/mehmet-bektas"&gt;Mehmet Bektas&lt;/a&gt; is a Senior Software Engineer at Netflix and a Jupyter Distinguished Contributor. He is the author of Notebook Intelligence, and contributes to JupyterLab, JupyterLab Desktop and several other projects in the Jupyter eco-system.&lt;/p&gt;
</content><category term="AI"/><category term="extensions"/><category term="JupyterLab"/></entry><entry><title>Introducing Notebook Intelligence!</title><link href="https://blog.jupyter.org/posts/2025/introducing-notebook-intelligence/" rel="alternate"/><published>2025-01-13T16:28:00+00:00</published><updated>2025-01-13T16:28:00+00:00</updated><author><name>Mehmet Bektas</name></author><id>tag:blog.jupyter.org,2025-01-13:/posts/2025/introducing-notebook-intelligence/</id><summary type="html">&lt;p&gt;I am thrilled to announce the release of Notebook Intelligence (NBI)! NBI is an AI coding assistant and extensible AI framework for JupyterLab.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;em&gt;Please note that this is not an official Jupyter subproject but an independent open-source tool for JupyterLab users who want to use GitHub Copilot as an AI coding assistant.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I am thrilled to announce the release of &lt;a href="https://github.com/mbektas/notebook-intelligence"&gt;Notebook Intelligence&lt;/a&gt; (NBI)! NBI is an AI coding assistant and extensible AI framework for JupyterLab. It uses &lt;a href="https://github.com/features/copilot"&gt;GitHub Copilot&lt;/a&gt; under the hood and is inspired by its design principles. NBI greatly boosts the productivity of JupyterLab users with AI assistance powered by GitHub Copilot.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Generate code" src="https://blog.jupyter.org/posts/2025/introducing-notebook-intelligence/images/001-1_X4bZbN5zydCMu6AJV3bP7A.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Generate code&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="generate-code-iterate-on-it"&gt;Generate code, iterate on it&lt;/h2&gt;
&lt;p&gt;NBI integrates tightly with the notebook document. Using cell toolbar item “Generate code” or keyboard shortcut “Cmd + G” / “Ctrl + G”, you can launch the inline coding assistant popover to generate code cells.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Generate code popover" src="https://blog.jupyter.org/posts/2025/introducing-notebook-intelligence/images/002-1_WnT4YBV94oGF_qvT7rpMZg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Generate code popover&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If the inline coding assistant is launched for a cell with existing code, then the generated code is shown in a diff view for approval.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Generate code with diff viewer" src="https://blog.jupyter.org/posts/2025/introducing-notebook-intelligence/images/003-1_4hM3aV5c3HycBY2HWQSaBQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Generate code with diff viewer&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If you are not satisfied with the generated code, you can re-generate with an updated prompt. Diff viewer also lets you edit the generated code manually before accepting it.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Fix code in cell with NBI" src="https://blog.jupyter.org/posts/2025/introducing-notebook-intelligence/images/004-1_8GgXLlEYjGtHNNw_m1VK4A.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Fix code in cell with NBI&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="explain-and-fix-code-troubleshoot-errors-reported"&gt;Explain and fix code, troubleshoot errors reported&lt;/h2&gt;
&lt;p&gt;NBI adds a new sub menu to notebook cell context menu. Copilot can explain code in a cell or suggest fixes for any issues in it. Clicking these menu items opens Copilot Chat and generates a suggestion.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Copilot context menu" src="https://blog.jupyter.org/posts/2025/introducing-notebook-intelligence/images/005-1_ItUuGiP1VvhgxQ4BzBDb5w.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Copilot context menu&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If the code cell has an output you can ask Copilot to explain it. If there are any errors reported, you can have Copilot to troubleshoot as well. These actions also take you to Copilot Chat interface.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Explain, fix, troubleshoot" src="https://blog.jupyter.org/posts/2025/introducing-notebook-intelligence/images/006-1_X7c10sZzH5qbv5kpAUOMBA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Explain, fix, troubleshoot&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="inline-completions"&gt;Inline Completions&lt;/h2&gt;
&lt;p&gt;Notebook Intelligence integrates with JupyterLab’s inline completion APIs and provides code suggestions as you type in a code cell or a Python file.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Inline completions" src="https://blog.jupyter.org/posts/2025/introducing-notebook-intelligence/images/007-1_EG7lvC6mjTyHLi6mcAGEIQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Inline completions&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Code suggestions are generated using GitHub Copilot. They are blazing fast and relevant to document you are working on. In addition to the code cell you are working on, the code in the surrounding cells are also used as context when generating suggestions.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Inline completions example" src="https://blog.jupyter.org/posts/2025/introducing-notebook-intelligence/images/008-1_BkIHAJsG4Fs7-8aD3k-4GA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Inline completions example&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="copilot-chat"&gt;Copilot Chat&lt;/h2&gt;
&lt;p&gt;NBI provides a user friendly chat interface to chat with GitHub Copilot. You can ask questions related to coding.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Copilot Chat" src="https://blog.jupyter.org/posts/2025/introducing-notebook-intelligence/images/009-1_ebEfHbxI6JnOWu9sSgP-nA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Copilot Chat&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If Copilot generates code snippets, they are rendered in a special format in a section with an action toolbar. Toolbar will have buttons to copy, insert, create new Python file and notebook from the snippet. Using these you can easily integrate the generated code into your project.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Copilot chat toolbar actions" src="https://blog.jupyter.org/posts/2025/introducing-notebook-intelligence/images/010-1_aAOuJZrObHMhZ2aaxhWRuA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Copilot chat toolbar actions&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="chat-commands"&gt;Chat Commands&lt;/h3&gt;
&lt;p&gt;Chat interface also provides commands to generate new notebooks and Python code files based on your task described in the prompt. Commands start with “/” and a command auto-complete list is shown as you type. You can navigate between the commands using keyboard and choose a suggestion using “Enter” or “Tab” keys.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Chat command auto-complete" src="https://blog.jupyter.org/posts/2025/introducing-notebook-intelligence/images/011-1_WBLVr1CtBxhVpMr2KYPVuQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Chat command auto-complete&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="newnotebook-command"&gt;/newNotebook command&lt;/h3&gt;
&lt;p&gt;You can generate new notebooks from a prompt with the &lt;strong&gt;/newNotebook&lt;/strong&gt; command. Notebook generation is shown interactively, a new empty notebook is created and opened, then code and markdown cells are added onto the notebook as they are generated by NBI and GitHub Copilot.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Generate notebook example" src="https://blog.jupyter.org/posts/2025/introducing-notebook-intelligence/images/012-1_Dkhv6nZt2vExa-UjMTkaRA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Generate notebook example&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="newpythonfile-command"&gt;/newPythonFile command&lt;/h3&gt;
&lt;p&gt;You can also create new Python files from a prompt using the &lt;strong&gt;/newPythonFile&lt;/strong&gt; command.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Generate Python file example" src="https://blog.jupyter.org/posts/2025/introducing-notebook-intelligence/images/013-1_H6fNVs50mmQcsYlTAE17bg.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Generate Python file example&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="getting-started-with-notebook-intelligence"&gt;Getting Started with Notebook Intelligence&lt;/h2&gt;
&lt;p&gt;Notebook Intelligence is a JupyterLab extension published as a Python package. Simply install the package and restart JupyterLab. NBI will add a new sidebar item for Copilot Chat, a notebook context sub-menu, a cell toolbar item for “Generate code” and a status bar item for GitHub Copilot login status to JupyterLab UI. It will also be integrated with inline completion (AI suggestions for code completion).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;notebook-intelligence
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="authentication-with-github-copilot"&gt;Authentication with GitHub Copilot&lt;/h2&gt;
&lt;p&gt;Notebook Intelligence requires a &lt;a href="https://github.com/features/copilot"&gt;GitHub Copilot&lt;/a&gt; subscription. NBI provides a user friendly interface to sign into your GitHub Copilot account from JupyterLab UI to activate access to your subscription.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="GitHub Copilot authentication" src="https://blog.jupyter.org/posts/2025/introducing-notebook-intelligence/images/014-1_5qQDXByT8dHSHMs86geeiQ.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;GitHub Copilot authentication&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="extensible-framework"&gt;Extensible Framework&lt;/h2&gt;
&lt;p&gt;Notebook Intelligence provides APIs to let developers extend its capabilities. You can add custom agents / chat participants, define tools (function calling) and add RAG capabilities to provide your own context to LLM for code / chat response generation. Stay tuned for my next blog post where I will walk you though extensibility features.&lt;/p&gt;
&lt;h2 id="try-it-out-and-share-your-feedback"&gt;Try it out and share your feedback!&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/mbektas/notebook-intelligence"&gt;Notebook Intelligence&lt;/a&gt; is currently in beta and designed for Python (support for more languages coming soon). Please try it out and share your feedback and any feature requests using project’s &lt;a href="https://github.com/mbektas/notebook-intelligence/issues"&gt;GitHub issues&lt;/a&gt;! User feedback from the community will shape the project’s roadmap.&lt;/p&gt;
&lt;div class="main-content-end" aria-hidden="true"&gt;&lt;/div&gt;
&lt;h2 id="about-the-author"&gt;About the Author&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/mehmet-bektas"&gt;Mehmet Bektas&lt;/a&gt; is a Senior Software Engineer at Netflix and a Jupyter Distinguished Contributor. He is the author of Notebook Intelligence, and contributes to JupyterLab, JupyterLab Desktop and several other projects in the Jupyter eco-system.&lt;/p&gt;
</content><category term="AI"/><category term="extensions"/><category term="JupyterLab"/></entry><entry><title>The Jupytext Menu is back!</title><link href="https://blog.jupyter.org/posts/2024/the-jupytext-menu-is-back/" rel="alternate"/><published>2024-01-31T20:29:00+00:00</published><updated>2024-01-31T20:29:00+00:00</updated><author><name>Marc Wouts</name></author><id>tag:blog.jupyter.org,2024-01-31:/posts/2024/the-jupytext-menu-is-back/</id><summary type="html">&lt;p&gt;A few weeks back, jupytext==1.16.0 went out. That release included multiple amazing contributions by Mahendra Paipuri, this post will go over the new features, and thank Mahendra for his great work.&lt;/p&gt;</summary><content type="html">&lt;p&gt;A few weeks back, &lt;code&gt;jupytext==1.16.0&lt;/code&gt; went out. That release included multiple amazing contributions by &lt;a href="https://github.com/mahendrapaipuri"&gt;Mahendra Paipuri&lt;/a&gt;, this post will go over the new features, and thank Mahendra for his great work.&lt;/p&gt;
&lt;p&gt;As we will see below, Mahendra restored the Jupytext Menu, made the extension fully compatible with JupyterLab 4 and Jupyter Notebook 7, and added the option to create Text Notebooks directly from the launcher.&lt;/p&gt;
&lt;h2 id="what-is-jupytext"&gt;What is Jupytext&lt;/h2&gt;
&lt;p&gt;Jupytext is a Python package that lets you save Jupyter Notebooks as text notebooks. Multiple formats are supported, and the notebooks can be saved either as &lt;a href="https://jupytext.readthedocs.io/en/latest/formats-markdown.html"&gt;Markdown documents&lt;/a&gt; with a &lt;code&gt;.md&lt;/code&gt; extension, or as &lt;a href="https://jupytext.readthedocs.io/en/latest/formats-scripts.html"&gt;scripts&lt;/a&gt; with e.g. a &lt;code&gt;.py&lt;/code&gt; extension (assuming you use Python - otherwise multiple &lt;a href="https://jupytext.readthedocs.io/en/latest/languages.html"&gt;languages&lt;/a&gt; are supported).&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;.py&lt;/code&gt; notebook in the percent format looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# %% [markdown]&lt;/span&gt;
&lt;span class="c1"&gt;# This is a markdown cell&lt;/span&gt;

&lt;span class="c1"&gt;# %%&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="c1"&gt;# %%&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To open a text notebook as a notebook in Jupyter, right-click on the document and select “Notebook” (you can also change the default viewer to “Jupytext Notebook” if you wish: see below the section about the settings)&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Right click to open a text notebook with the Notebook editor" src="https://blog.jupyter.org/posts/2024/the-jupytext-menu-is-back/images/001-1_OekPCzL8Obv0NYracG_-uA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Right click to open a text notebook with the Notebook editor&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Text notebooks are conveniently edited and executed in Jupyter as notebooks:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="A Text Notebook in JupyterLab" src="https://blog.jupyter.org/posts/2024/the-jupytext-menu-is-back/images/002-1_J6-ToiGplGQFE0eQTWo0iw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;A Text Notebook in JupyterLab&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;You can also edit them with your favorite text editor. You will get the changes back in Jupyter by re-opening or by &lt;em&gt;reloading&lt;/em&gt; the document: click e.g. on &lt;em&gt;reload Python file from disk&lt;/em&gt; in the &lt;em&gt;File&lt;/em&gt; menu.&lt;/p&gt;
&lt;p&gt;Text notebooks only contain the notebook inputs. For that reason they use much less disk space than &lt;code&gt;.ipynb&lt;/code&gt; notebooks. They are also better suited for version control as they only contain the content that was actually typed by the user. But even more useful are &lt;em&gt;paired&lt;/em&gt; notebooks: text notebooks paired with an &lt;code&gt;.ipynb&lt;/code&gt; notebook where the notebook outputs are preserved. When a paired notebook is saved, Jupytext writes the notebook to both files. When the notebook is read or reloaded in Jupyter, the notebook inputs are loaded from the most recent file, meaning that any edits on the text notebook will be reflected in Jupyter (and propagated to the &lt;code&gt;.ipynb&lt;/code&gt; file the next time it is saved).&lt;/p&gt;
&lt;h2 id="the-jupytext-menu-is-back"&gt;The Jupytext Menu is back!&lt;/h2&gt;
&lt;p&gt;One of the most frequent operation when using Jupytext is to &lt;em&gt;pair&lt;/em&gt; an &lt;code&gt;.ipynb&lt;/code&gt; notebook with a text notebook in the format of your choice.&lt;/p&gt;
&lt;p&gt;In earlier versions of Jupytext, pairing a notebook had to be done through the command palette.&lt;/p&gt;
&lt;p&gt;In Jupytext v1.16, thanks to Mahendra’s work on the front-end extension, you can directly use the Jupytext Menu for this. And the menu is available for both JupyterLab 4 and Jupyter Notebook 7!&lt;/p&gt;
&lt;p&gt;If you are new to Jupytext, we recommend that you pair your &lt;code&gt;.ipynb&lt;/code&gt; notebooks with either a &lt;em&gt;percent script&lt;/em&gt; or with a MyST Markdown file. The former format works well if you want to save and edit your notebook as a script, while the latter is well suited for writing documentation.&lt;/p&gt;
&lt;p&gt;Once you have paired the notebook with a Jupytext format, save your notebook, and the paired files will be created or updated.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="The Jupytext Menu" src="https://blog.jupyter.org/posts/2024/the-jupytext-menu-is-back/images/003-1_luqGwTI9TbORaPjfsquNIQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;The Jupytext Menu&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="creating-new-text-notebooks"&gt;Creating new text notebooks&lt;/h2&gt;
&lt;p&gt;For some notebooks you might decide that you don’t need to save the outputs at all. In that case you can work with an (unpaired) text notebook.&lt;/p&gt;
&lt;p&gt;To create a text notebook you can use the &lt;em&gt;New Text Notebook&lt;/em&gt; sub-menu under &lt;em&gt;File&lt;/em&gt;:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Create Text Notebooks directly from the menu" src="https://blog.jupyter.org/posts/2024/the-jupytext-menu-is-back/images/004-1_YLrRNo1MKSPrUSo1wwjsBQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Create Text Notebooks directly from the menu&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If, later on, you decide that you want to preserve the notebook outputs in a &lt;em&gt;paired&lt;/em&gt; &lt;code&gt;.ipynb&lt;/code&gt; notebooks, you will just have to &lt;em&gt;pair&lt;/em&gt; the document to an &lt;code&gt;.ipynb&lt;/code&gt; notebook using the Jupytext Menu documented at the previous paragraph.&lt;/p&gt;
&lt;h2 id="text-notebooks-in-the-launcher"&gt;Text Notebooks in the launcher&lt;/h2&gt;
&lt;p&gt;In Jupytext v1.16, thanks to Mahendra’s work on the front end, text notebooks are also available in a Jupytext section in the Jupyter launcher:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Text Notebooks are also available in the launcher" src="https://blog.jupyter.org/posts/2024/the-jupytext-menu-is-back/images/005-1_P_0hx6p-nfwkDpqHGJmewg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Text Notebooks are also available in the launcher&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="jupytext-settings-in-jupyterlab"&gt;Jupytext Settings in JupyterLab&lt;/h2&gt;
&lt;p&gt;Since there are many possible formats for text notebooks, we have decided to expose only the most common ones, by default, in the launcher and in the &lt;em&gt;New Text Notebook&lt;/em&gt; menu.&lt;/p&gt;
&lt;p&gt;You can include more formats in the menu and in the launcher by changing the Jupytext settings in the &lt;em&gt;Settings Editor&lt;/em&gt; (in the &lt;em&gt;Settings&lt;/em&gt; menu):&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="The Jupytext Menu/Launcher settings" src="https://blog.jupyter.org/posts/2024/the-jupytext-menu-is-back/images/006-1_n31jAKcy96wrrTYR3qCuXA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;The Jupytext Menu/Launcher settings&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;You might also want to open certain text documents as notebooks with a single click. You can achieve this by setting the default viewer for those documents to “Jupytext Notebook”. For instance, if you want to open &lt;code&gt;.py&lt;/code&gt; and &lt;code&gt;.md&lt;/code&gt; files as notebooks with a single click, you can configure the default viewers like this:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Changing the default viewers to open Text Notebooks with a single click" src="https://blog.jupyter.org/posts/2024/the-jupytext-menu-is-back/images/007-1_1oj4Jrdz8hOhG90rYZckEA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Changing the default viewers to open Text Notebooks with a single click&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Alternatively, you can also list and set the default viewers with the &lt;code&gt;jupytext-config&lt;/code&gt; utility (which was developed recently by Thierry Parmentelat), using e.g.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;jupytext-config&lt;span class="w"&gt; &lt;/span&gt;set-default-viewer&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;markdown
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;to set “Jupytext Notebook” as the default viewer for both Python and Markdown files.&lt;/p&gt;
&lt;h2 id="pairing-notebooks-globally"&gt;Pairing notebooks globally&lt;/h2&gt;
&lt;p&gt;The pairing commands provided through the Jupytext Menu and commands act on individual notebooks, by settings a &lt;code&gt;jupytext.formats&lt;/code&gt; metadata in the notebook.&lt;/p&gt;
&lt;p&gt;It is also possible to pair all the notebooks within a certain folder using a &lt;code&gt;jupytext.toml&lt;/code&gt; configuration file - see &lt;a href="https://jupytext.readthedocs.io/en/latest/config.html"&gt;Jupytext’s documentation&lt;/a&gt;. Please note that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The local metadata in the notebook takes precedence over the global configuration, and&lt;/li&gt;
&lt;li&gt;Jupyter is aware of the global &lt;code&gt;jupytext.toml&lt;/code&gt; file and will pair the notebooks accordingly, however the Jupytext Menu is not aware of the global configuration, so the paired formats selected through the global configuration will not be checked in the menu.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="installing-jupytext-v116"&gt;Installing Jupytext v1.16&lt;/h2&gt;
&lt;p&gt;The front end extension for JupyterLab shipped with Jupytext v1.16 requires JupyterLab 4, and/or Jupyter Notebook 7. Please upgrade Jupyter accordingly. Then, install Jupytext with&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;jupytext&amp;gt;=1.16.0&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;conda&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;jupytext&amp;gt;=1.16.0&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;conda-forge
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;and restart your Jupyter server with e.g. &lt;code&gt;jupyter lab&lt;/code&gt;.&lt;/p&gt;
&lt;div class="main-content-end" aria-hidden="true"&gt;&lt;/div&gt;
&lt;h2 id="acknowledgments"&gt;Acknowledgments&lt;/h2&gt;
&lt;p&gt;I would like to thank &lt;a href="https://github.com/mahendrapaipuri"&gt;Mahendra Paipuri&lt;/a&gt; for his impressive work. I am pretty sure that the Jupytext Menu, and the option to create new text notebooks, will be much appreciated by Jupytext users!&lt;/p&gt;
&lt;p&gt;While users will mostly notice Mahendra’s work on the front end extension, it was actually not his only contribution to this release! Mahendra also thoroughly revisited the packaging of Jupytext, and helped us transition the project to the &lt;code&gt;src&lt;/code&gt; layout, and our old &lt;code&gt;setup.py&lt;/code&gt; to an up-to-date &lt;code&gt;pyproject.toml&lt;/code&gt; configuration that uses &lt;code&gt;hatch&lt;/code&gt; to build Jupytext.&lt;/p&gt;
&lt;p&gt;I also want to thank &lt;a href="https://github.com/LecrisUT"&gt;Cristian Le&lt;/a&gt; for his precious advice regarding the layout refactoring, and for helping us to tackle the CI reorganization. &lt;a href="https://github.com/parmentelat"&gt;Thierry Parmentelat&lt;/a&gt;, who had previously ported the front-end extension to JupyterLab 4, contributed much testing and feedback on this new version of the front-end extension.&lt;/p&gt;
&lt;p&gt;It is always a pleasure for me to maintain Jupytext, an amazing adventure that started five years ago already. However, I can get busy at times (Jupytext comes in addition to my day job, one cat, two bikes, three kids), so I am really thankful for receiving such contributions, especially when they are of such a great quality!&lt;/p&gt;
</content><category term="extensions"/><category term="JupyterLab"/><category term="notebook format"/></entry><entry><title>Generative AI in Jupyter</title><link href="https://blog.jupyter.org/posts/2023/generative-ai-in-jupyter/" rel="alternate"/><published>2023-08-02T16:15:00+00:00</published><updated>2023-08-16T00:33:00+00:00</updated><author><name>Jason Weill</name></author><id>tag:blog.jupyter.org,2023-08-02:/posts/2023/generative-ai-in-jupyter/</id><summary type="html">&lt;p&gt;Jupyter AI, a new open source project, brings generative artificial intelligence to notebooks with magic commands and a chat interface.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;a href="https://github.com/jupyterlab/jupyter-ai"&gt;Jupyter AI&lt;/a&gt; brings generative artificial intelligence to Jupyter notebooks, giving users the power to explain and generate code, fix errors, summarize content, ask questions about their local files, and generate entire notebooks from a natural language prompt. Using its powerful magic commands and chat interface, Jupyter AI connects Jupyter with large language models (LLM) from providers such as AI21, Anthropic, AWS, Cohere, and OpenAI. We use LangChain to support all popular LLMs and providers, giving you access to new models as they are released. LangChain will let Jupyter AI use local models as well. Jupyter AI version 1.0, for JupyterLab 3, and Jupyter AI 2.0, for JupyterLab 4, are now available as free and open source software.&lt;/p&gt;
&lt;p&gt;Jupyter AI is designed with responsible AI and data privacy in mind. You can pick which LLM and embedding model best suit your needs. The underlying prompts, chains, and other software are open source, so you can see exactly how your data is being used. Jupyter AI saves metadata about model-generated content in each AI-generated code cell, so you and your collaborators can track where AI-generated code enters your workflow. Finally, Jupyter AI only contacts an LLM when you ask it to, directly; it does not read your data or transmit it to models without your explicit consent.&lt;/p&gt;
&lt;p&gt;Jupyter AI is an official subproject of Project Jupyter and is available now as free, open source software. We designed it based on the same principles that underlie all of Project Jupyter: we made it simple, easy to use, modular, and extensible, and we prioritized ethical considerations and social responsibility. We’d love to hear from you about how we can improve it!&lt;/p&gt;
&lt;h2 id="getting-started-with-jupyter-ai"&gt;Getting started with Jupyter AI&lt;/h2&gt;
&lt;p&gt;Start using Jupyter AI by installing the appropriate version with &lt;code&gt;pip&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;jupyter-ai&amp;gt;=1.0,&amp;lt;2.0&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# If you use JupyterLab 3&lt;/span&gt;
pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;jupyter-ai&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="c1"&gt;# If you use JupyterLab 4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then, launch JupyterLab. Jupyter AI provides two different interfaces to interact with LLMs. In JupyterLab, you can converse with a chat UI to assist you with your code. Also, in any supported notebook or IPython environment, including JupyterLab, Notebook, IPython, Colab, and Visual Studio Code, you can invoke LLMs using the &lt;code&gt;%%ai&lt;/code&gt; magic command. Jupyter AI can turn any Jupyter Notebook session into a generative AI playground with support for text and image models.&lt;/p&gt;
&lt;p&gt;Project Jupyter is vendor-neutral, so Jupyter AI supports LLMs from AI21, Anthropic, AWS, Cohere, HuggingFace Hub, and OpenAI. More model providers will be added in the future. Please review a provider’s privacy policy and pricing model before you use it. We’re also working on support for locally-deployed models, for maximum privacy. Once you have installed Jupyter AI, before you can use magic commands, you will need to authenticate to each model provider that you wish to use. For most providers, this involves setting an environment variable. The user documentation has detailed instructions for &lt;a href="https://jupyter-ai.readthedocs.io/en/latest/users/index.html#model-providers"&gt;configuring model providers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The chat interface has its own configuration panel for choosing a language model and an embedding model, and for authenticating to each model’s provider. A language model responds to users’ messages in the chat panel. When you ask the chat interface to learn about local files, it uses an embedding model to parse these files and to assist when you ask questions about them.&lt;/p&gt;
&lt;p&gt;You can find full details about how to configure and use Jupyter AI in the &lt;a href="https://jupyter-ai.readthedocs.io/en/latest/users/index.html#"&gt;user documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="the-chat-interface-your-ai-assistant"&gt;The chat interface, your AI assistant&lt;/h2&gt;
&lt;p&gt;The chat interface puts you in conversation with &lt;strong&gt;Jupyternaut&lt;/strong&gt;, a conversational agent using a language model of your choice.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2023/generative-ai-in-jupyter/images/001-0_LKAfjrw26xlrAJ-s.webp" alt="A screenshot of JupyterLab showing Jupyter AI in the left panel, with a help message shown to the user." loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;Jupyternaut communicates primarily through text, and it can also interact with files in JupyterLab. It can answer questions as a general-purpose AI assistant, include selections from your notebooks with your questions, insert AI-generated output into your notebooks, learn from and ask questions about your local files, and generate notebooks from a prompt. Jupyternaut can only see the information you send it by sending chat commands; it only reads your data when you specifically ask it to.&lt;/p&gt;
&lt;p&gt;To get started, you can ask Jupyternaut a question:&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2023/generative-ai-in-jupyter/images/002-0_SBrRAW0NdUWIcj3B.webp" alt="Partial screenshot of Jupyter AI showing a conversation between jweill and Jupyternaut, in which jweill has asked “What’s the difference between a tuple and a list in Python?” and Jupyternaut has provided a detailed response with a code example." loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;You can also highlight part of your notebook and include it with your prompt.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2023/generative-ai-in-jupyter/images/003-0_ppPDPJyDZ5vjprCP.webp" alt="Screenshot of JupyterLab with some source code highlighted. The user has typed the question “What does this code do?” and has checked a checkbox to include the selection with their question." loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;Using prompts that include the selected code, you can ask Jupyternaut to explain your code in plain English (or in any other language it can speak), make modifications to it, and identify errors in it. If you want, Jupyternaut can even replace your selection with its response. Please review AI-generated code before you run it, as you would review code written by another person.&lt;/p&gt;
&lt;p&gt;For example, you can ask Jupyternaut to rewrite code by adding comments to it:&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2023/generative-ai-in-jupyter/images/004-0_lWJIaPn8REBWHPHV.webp" alt="Partial screenshot of JupyterLab with some source code selected. The user has typed the prompt “Rewrite this code with comments added to it”, and has checked the boxes for “include selection” and “replace selection”" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;Jupyternaut sends the code to your chosen language model, then replaces the selection with the language model’s response.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2023/generative-ai-in-jupyter/images/005-0_1AQQ9XR-JQ2oqZ10.jpg" alt="Partial screenshot of JupyterLab with source code that has been rewritten with comments" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;h2 id="generating-a-notebook-from-a-text-prompt"&gt;Generating a notebook from a text prompt&lt;/h2&gt;
&lt;p&gt;Jupyter AI’s chat interface can generate an entire notebook from a text prompt. To do this, run the /generate command and provide a text description. Jupyternaut will use its AI language model to name the workbook and fill it with markdown and code cells. This may take a few minutes. While Jupyternaut is working, you can continue to use the chat UI, and Jupyternaut will continue generating your notebook.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2023/generative-ai-in-jupyter/images/006-0_Ln2poeiOXHq2mlkw.webp" alt="Screenshot of a user executing a /generate command in Jupyter AI to generate a demonstration of how to use Matplotlib. Jupyternaut provides a response that it is starting to generate a notebook, then another response that it has generated a notebook" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;Once Jupyternaut has finished generating your notebook, it will send you a message with its filename, so that you can open it. Please review any AI-driven code before you run it.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2023/generative-ai-in-jupyter/images/007-0_jdpMEQCErUeEtzO_.webp" alt="Screenshot of JupyterLab showing “Matplotlib: A Comprehensive Guide”, a Jupyter notebook generated by Jupyter AI" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;h2 id="learning-from-and-asking-about-local-files"&gt;Learning from and asking about local files&lt;/h2&gt;
&lt;p&gt;You can use the &lt;code&gt;/learn&lt;/code&gt; command to teach Jupyternaut about local files, so that you can use the &lt;code&gt;/ask&lt;/code&gt; command to ask questions about them. For example, using the &lt;code&gt;/learn&lt;/code&gt; command, you can teach Jupyternaut about Jupyter AI’s documentation:&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2023/generative-ai-in-jupyter/images/008-0_PxPcfM_mb_ZfsfRi.webp" alt="Screenshot of Jupyter AI having run a /learn command to learn the contents of the “docs” directory" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;When you learn local files, Jupyternaut uses an embedding model to convert data, then stores the output in a local vector database. Please review the privacy policy for each model, and be aware of any restrictions about sharing your local data with third-party model providers. Once the learning process is complete, you can ask a question with the &lt;code&gt;/ask&lt;/code&gt; command. Using retrieval-augmented generation (RAG), Jupyternaut will append relevant info to your question from its vector database, then it will use the AI language model you selected to answer your question.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2023/generative-ai-in-jupyter/images/009-0_fXKXFT2dq2P4yK4B.webp" alt="Screenshot of Jupyter AI answering an “/ask” command based on the data it has previously learned from local files" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;h2 id="notebooks-as-generative-ai-playgrounds-with-magic-commands"&gt;Notebooks as generative AI playgrounds with magic commands&lt;/h2&gt;
&lt;p&gt;Jupyter AI also provides magic commands that you can run in notebook cells and in the IPython command-line interface. To get started, run &lt;code&gt;%load_ext jupyter_ai_magics&lt;/code&gt;, which will load the magics extension. You can then use Jupyter AI with the &lt;code&gt;%%ai&lt;/code&gt; magic command. You can run &lt;code&gt;%ai help&lt;/code&gt; to learn about all the options and commands you can run using the &lt;code&gt;%ai&lt;/code&gt; line magic and &lt;code&gt;%%ai&lt;/code&gt; cell magic commands.&lt;/p&gt;
&lt;p&gt;Each &lt;code&gt;%%ai&lt;/code&gt; command requires a model, typically specified as &lt;code&gt;provider-id:model-id&lt;/code&gt;. To use a particular provider, you’ll need to set its API key using an appropriate environment variable or Python module. See the &lt;a href="https://jupyter-ai.readthedocs.io/en/latest/users/index.html#model-providers"&gt;model providers section of the user documentation&lt;/a&gt; for specific information. Once you’ve provided the key to your model provider, you can run a magic command by specifying the model on the first line and specifying your prompt on subsequent lines.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2023/generative-ai-in-jupyter/images/010-0_J1CWCz_eo5_vJWu0.webp" alt="Partial screenshot of JupyterLab running an %%ai magic command using the Anthropic Claude-v1.2 model" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;You can use the &lt;code&gt;-f&lt;/code&gt; or &lt;code&gt;--format&lt;/code&gt; parameter to customize the format of the output, including HTML, math, source code, and images.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2023/generative-ai-in-jupyter/images/011-0_T8EepY5jfigS67Yn.webp" alt="Screenshot of JupyterLab running three %%ai magic commands to output content in HTML, math, and image formats" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;You can interpolate a variable name or expression in a prompt by enclosing it in braces (curly brackets).&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2023/generative-ai-in-jupyter/images/012-0_jPgHUMJegDgaQUNt.webp" alt="Screenshot showing JupyterLab running an %%ai magic command with variable interpolation" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;Interpolation also works with the special &lt;code&gt;In&lt;/code&gt; and &lt;code&gt;Out&lt;/code&gt; variables, which contain the inputs and outputs of code cells. Note that a cell output can contain both text and markdown values.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2023/generative-ai-in-jupyter/images/013-0_su7ZC4EpVmK2FRxg.webp" alt="Screenshot of JupyterLab running two %%ai magic commands: the first one generates output, and the second one uses interpolation to incorporate the first command’s output into its prompt" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;Jupyter AI adds a special &lt;code&gt;Err&lt;/code&gt; variable, which stores the errors that occur while executing code. By interpolating this variable into a prompt, you can use an AI language model to explain and correct an error in your code.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2023/generative-ai-in-jupyter/images/014-0_4fix_RU095C7PqZB.webp" alt="Screenshot of JupyterLab having run a cell with an error in it, then having run a cell that interpolates the error into a prompt" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;h2 id="about-the-developers"&gt;About the developers&lt;/h2&gt;
&lt;p&gt;Jupyter AI is an officially supported Jupyter subproject. The following Jupyter contributors built Jupyter AI.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Brian Granger is a Senior Principal Technologist at AWS. Brian is a cofounder of Project Jupyter, a member of the Jupyter Executive Council, and a board member of the PyTorch Foundation." src="https://blog.jupyter.org/posts/2023/generative-ai-in-jupyter/images/015-1_WC29kYZhf40VeGfM7muopg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;&lt;a href="https://github.com/ellisonbg"&gt;Brian Granger&lt;/a&gt; is a Senior Principal Technologist at AWS. Brian is a cofounder of Project Jupyter, a member of the Jupyter Executive Council, and a board member of the PyTorch Foundation.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img alt="Andrii Ieroshenko is a Software Development Engineer at AWS. Andrii is a Project Jupyter contributor and JupyterLab Council member." src="https://blog.jupyter.org/posts/2023/generative-ai-in-jupyter/images/016-0_zMXhUHT0SrteMJDo.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;&lt;a href="https://github.com/andrii-i/"&gt;Andrii Ieroshenko&lt;/a&gt; is a Software Development Engineer at AWS. Andrii is a Project Jupyter contributor and JupyterLab Council member.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img alt="Piyush Jain is a Software Development Engineer at AWS working on open source contributions to Project Jupyter. He is also a member of the Jupyter Server team, actively participating in maintenance and development of the Jupyter Server project." src="https://blog.jupyter.org/posts/2023/generative-ai-in-jupyter/images/017-0_wDtguQqxfEHtzgzR.jpg" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;&lt;a href="https://github.com/3coins"&gt;Piyush Jain&lt;/a&gt; is a Software Development Engineer at AWS working on open source contributions to Project Jupyter. He is also a member of the Jupyter Server team, actively participating in maintenance and development of the Jupyter Server project.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img alt="David Qiu is a Software Development Engineer at AWS working on Project Jupyter. Previously, David was researching physical chemistry at the University of Illinois at Urbana-Champaign." src="https://blog.jupyter.org/posts/2023/generative-ai-in-jupyter/images/018-0_hfX4LQ_08sjeDoKE.jpg" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;&lt;a href="https://github.com/dlqqq/"&gt;David Qiu&lt;/a&gt; is a Software Development Engineer at AWS working on Project Jupyter. Previously, David was researching physical chemistry at the University of Illinois at Urbana-Champaign.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img alt="Jason Weill is a Senior Front-End Engineer at AWS; a contributor to JupyterLab, Jupyter Governance, and Jupyter Scheduler; and a member of the JupyterLab Council, the Jupyter Security Working Group, and the Jupyter Diversity, Equity, and Inclusion Standing Committee." src="https://blog.jupyter.org/posts/2023/generative-ai-in-jupyter/images/019-0_65ivwhUugkG0UUR8.jpg" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;&lt;a href="https://github.com/JasonWeill"&gt;Jason Weill&lt;/a&gt; is a Senior Front-End Engineer at AWS; a contributor to JupyterLab, Jupyter Governance, and Jupyter Scheduler; and a member of the JupyterLab Council, the Jupyter Security Working Group, and the Jupyter Diversity, Equity, and Inclusion Standing Committee.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="how-can-you-help"&gt;How can you help?&lt;/h2&gt;
&lt;p&gt;We’re just getting started with generative AI in Jupyter. Please join us! We have &lt;a href="https://github.com/jupyterlab/jupyter-ai/issues"&gt;a list of issues&lt;/a&gt; that could use your help, such as &lt;a href="https://github.com/jupyterlab/jupyter-ai/issues/190"&gt;adding support for locally hosted LLMs&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Install and use the &lt;a href="https://github.com/jupyterlab/jupyter-ai"&gt;Jupyter AI&lt;/a&gt; extension.&lt;/strong&gt; If you find any bugs or have suggestions, please create issues on GitHub.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Join the discussion&lt;/strong&gt; about generative AI in Jupyter in the “Generative AI in Jupyter” topic on Discourse.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contribute:&lt;/strong&gt; Your bug reports, feature requests, and pull requests will help improve this project for everyone.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Note: An earlier version of this story said that users can choose which vector database to use with Jupyter AI. As of Jupyter AI 2.1.0, only FAISS is available; users cannot choose another vector database. Sorry for the error.&lt;/em&gt;&lt;/p&gt;
&lt;div class="main-content-end" aria-hidden="true"&gt;&lt;/div&gt;
</content><category term="AI"/><category term="extensions"/><category term="JupyterLab"/></entry><entry><title>A theme editor for JupyterLab</title><link href="https://blog.jupyter.org/posts/2023/a-theme-editor-for-jupyterlab/" rel="alternate"/><published>2023-06-19T08:11:00+00:00</published><updated>2023-06-19T08:11:00+00:00</updated><author><name>Florence Haudin</name></author><id>tag:blog.jupyter.org,2023-06-19:/posts/2023/a-theme-editor-for-jupyterlab/</id><summary type="html">&lt;p&gt;JupyterLab is a comprehensive web user interface for scientific and technical computing providing tools such as notebooks, text editors, consoles, and terminals. Like many similar tools, it allows users to choose from light or dark color themes.&lt;/p&gt;</summary><content type="html">&lt;p&gt;JupyterLab is a comprehensive web user interface for scientific and technical computing providing tools such as &lt;a href="https://jupyterlab.readthedocs.io/en/stable/user/notebook.html#notebook"&gt;notebooks&lt;/a&gt;, text editors, consoles, and terminals. Like many similar tools, it allows users to choose from light or dark color themes. However, users may want to further adjust the looks of the interface, just for fun or for specific personal preferences or needs.&lt;/p&gt;
&lt;p&gt;The ability to fine-tune contrast, color palettes, and fonts can be very useful for accessibility, an essential requirement for software to be usable by the whole community. There is an ongoing &lt;a href="https://jupyter-accessibility.readthedocs.io/"&gt;project-wide effort&lt;/a&gt; to improve accessibility in Jupyter. Choices of color palettes impact color-blind users while font choices can have a significant impact on people affected by dyslexia, or who suffer from migraines.&lt;/p&gt;
&lt;p&gt;This diversity of requirements and preferences shows that end users should have the means to adjust parameters and tweak existing themes. Subtle differences can improve comfort significantly.&lt;/p&gt;
&lt;h2 id="jupyterlab-themes"&gt;JupyterLab themes&lt;/h2&gt;
&lt;p&gt;Theming in JupyterLab is enabled by the fact that most of the layout and colors of the UI are defined by a set of CSS variables. There is no need for a complete stylesheet: one can set values for the base parameters from which the appearance of the user interface is derived. This system ensures a consistent look and feel throughout the application.&lt;/p&gt;
&lt;p&gt;Custom themes provide a set of values for the base CSS variables and package the resulting CSS file in JupyterLab extensions. &lt;a href="https://github.com/search?q=jupyter+lab+theme"&gt;A search on GitHub&lt;/a&gt; returns 29 repositories defining such custom themes for JupyterLab.&lt;/p&gt;
&lt;p&gt;Creating a theme requires implementing values for &lt;em&gt;dozens&lt;/em&gt; of base CSS variables. This is often achieved by theme authors tweaking the values given in the default dark or light themes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Generating a consistent palette, testing, and packaging the outcome in the form of a JupyterLab extension requires development skills and is neither direct nor trivial.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="a-new-tool-for-authoring-jupyterlab-themes"&gt;A new tool for authoring JupyterLab themes&lt;/h2&gt;
&lt;p&gt;To lower the bar for customizing JupyterLab we created a new tool providing a simple interface for tuning the JupyterLab appearance interactively, allowing theme authors to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;use the selected colors in their application and “pin down” the result in the configuration,&lt;/li&gt;
&lt;li&gt;export the outcome in a form amenable to packaging into a new JupyterLab theme.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The theme editor extension is a theme prototyping tool, a sandbox to test changes in colors, font family and font size, and a configuration tool for end users. It displays a reduced set of parameters one can play with, allowing users to select:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;colors through color pickers,&lt;/li&gt;
&lt;li&gt;numerical values from sliders (for font size, border radius and width),&lt;/li&gt;
&lt;li&gt;font families from a predefined dropdown list.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A screenshot of JupyterLab with the theme editor panel is displayed in Figure 1, with a notebook opened in light theme. Figure 2 shows different screenshots with different custom themes.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Screenshot of JupyterLab interface with the theme editor on the left panel and a notebook opened. The theme chosen is the light default one." src="https://blog.jupyter.org/posts/2023/a-theme-editor-for-jupyterlab/images/001-0_vj7tjopuS2GvZg3N.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Figure 1: The theme editor extension in JupyterLab.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img alt="4 screenshots of the JupyterLab interface are shown to illustrate the kinds of themes that can be generated by the theme editor, with gray, blue, orange and pink tones and different font families." src="https://blog.jupyter.org/posts/2023/a-theme-editor-for-jupyterlab/images/002-0_5OLBiA8tmTbD1Nan.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Figure 2: Examples of dynamically editing the color and fonts scheme of JupyterLab with the theme editor.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="color-palettes"&gt;Color palettes&lt;/h2&gt;
&lt;p&gt;For all the colors (accent, brand, border, error, info, layout, success, warn), the same logic is applied. The user picks a base color and a palette is automatically calculated using Microsoft’s &lt;a href="https://www.fast.design/docs/api/fast-colors.colorpalette"&gt;fast-colors&lt;/a&gt; library.&lt;/p&gt;
&lt;p&gt;Let’s take the specific example of the layout colors impacting the background of most elements of the interface. They are defined using &lt;code&gt;--jp-layout-color[i]&lt;/code&gt; CSS variables from white to light grays (for the light theme).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;--jp-layout-color0&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;white&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;--jp-layout-color1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;white&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;--jp-layout-color2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--md-grey-200&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;--jp-layout-color3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--md-grey-400&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;--jp-layout-color4&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--md-grey-600&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The number of colors in the palette fits the number of colors in the CSS set of variables (here the palette contains 5 colors). There are different ways to define colors (hexadecimal, RGBA, HSLA). The latter format has the advantage of giving direct access to luminance &lt;em&gt;L&lt;/em&gt;, a measure of the lightness of a given color.&lt;/p&gt;
&lt;p&gt;An inverse layout palette is computed for use with most text elements. The luminance of the inverse layout color &lt;em&gt;L’&lt;/em&gt; is calculated as &lt;em&gt;1-L&lt;/em&gt; plus a correction depending on how &lt;em&gt;1-L&lt;/em&gt; is close to 0.5. There isn’t a general CSS rule in JupyterLab concerning elements on top of a background but some PRs were proposed to reinforce the coupling between inverse layout elements and layout backgrounds with the same color index. The contrast still needs to be improved though, by using other palettes or better corrections when defining the inverse layout colors.&lt;/p&gt;
&lt;p&gt;The full process just described from picking a layout color to resulting palettes and the corresponding interface is illustrated in Figure 3.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="The figure is divided into 3 parts. Part 1 is a screenshot with color picker with a green color selected. Part 2 is a capture showing the resulting palette calculated from this color choice. Finally, part 3 is a screenshot of JupyterLab interface with the different green tones calculated from the base color that has been picked." src="https://blog.jupyter.org/posts/2023/a-theme-editor-for-jupyterlab/images/003-0_vD9AcycD3qSCAJgu.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Figure 3: From a color to the layout, and inverse layout palettes with the resulting interface.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="font-size-and-font-family"&gt;Font size and font family&lt;/h2&gt;
&lt;p&gt;As mentioned previously, font size and font family can be tuned too. Let’s take the example of the &lt;code&gt;--jp-ui-font-size[i]&lt;/code&gt; CSS variables defining the text size of most of the text elements in the interface:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;--jp-ui-font-scale-factor&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;2&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;--jp-ui-font-size0&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;8333em&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;--jp-ui-font-size1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;13px&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;/* Base font size */&lt;/span&gt;
&lt;span class="nt"&gt;--jp-ui-font-size2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;2em&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;--jp-ui-font-size3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;44em&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A slider lets the user control the base font size associated with &lt;code&gt;--jp-ui-font-size1&lt;/code&gt;. The other font sizes are then calculated by applying a scale factor to the base font.&lt;/p&gt;
&lt;p&gt;Concerning font families, a non-exhaustive list is proposed (i.e., default browser fonts or Google fonts). It includes both standard accessible fonts (Arial, Courier, Helvetica, Sans Serif, system-ui, Times New Roman, Verdana) and less usual ones to create artistic or special interfaces with cursive fonts ( for instance Dancing Script or Single Day). Only 2 fonts are proposed for code: Space Mono and monospace.&lt;/p&gt;
&lt;h2 id="storing-the-new-theme"&gt;Storing the new theme&lt;/h2&gt;
&lt;p&gt;Once satisfied with a new theme, users can export it using a button at the top of the theme editor panel. This creates a &lt;em&gt;variable.css&lt;/em&gt; file that can be packaged in a theme extension (see the &lt;a href="https://github.com/jupyterlab/extension-cookiecutter-ts/"&gt;extension template&lt;/a&gt;). The new parameters are also synchronously saved in the settings editor and can be restored for the next JupyterLab opening if the boolean &lt;em&gt;useSettings&lt;/em&gt; is set to true. If not, the interface will look like in Figure 1: with light theme default CSS values. Resetting &lt;em&gt;useSettings&lt;/em&gt; to true will restore the formerly tuned parameters.&lt;/p&gt;
&lt;h2 id="future-development"&gt;Future development&lt;/h2&gt;
&lt;p&gt;The look and feel of the theme editor UI still needs to be improved. We will work on unifying the styling of all interfaces making use of &lt;a href="https://github.com/rjsf-team/react-jsonschema-form"&gt;react-jsonschema-form&lt;/a&gt;. It is already used for the JupyterLab settings editor, the notebook metadata editor of JupyterLab 4.0, and several extensions.&lt;/p&gt;
&lt;p&gt;Moreover, the current implementation is bound to fast-colors palettes and we may want to use a different approach, like decoupling the layout and inverse layout palettes to reach better contrasts or give more freedom in the color choices.&lt;/p&gt;
&lt;p&gt;Any help (e.g., filling issues for bugs or enhancement requests, opening pull requests) to improve &lt;a href="https://github.com/jupyterlab-contrib/jupyterlab-theme-editor"&gt;the extension&lt;/a&gt; is welcome.&lt;/p&gt;
&lt;h2 id="try-it-out"&gt;Try it out&lt;/h2&gt;
&lt;p&gt;You can install the &lt;a href="https://pypi.org/project/jupyter-theme-editor"&gt;PyPI package&lt;/a&gt; by running:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;jupyter_theme_editor
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can also try the theme editor on &lt;a href="https://mybinder.org/v2/gh/jupyterlab-contrib/jupyterlab-theme-editor/main?urlpath=lab"&gt;Binder&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="aknowledgement"&gt;Aknowledgement&lt;/h2&gt;
&lt;p&gt;Many thanks to &lt;a href="https://twitter.com/ihuicatls"&gt;Isabel Paredes&lt;/a&gt; for drawing the palette icon!&lt;/p&gt;
&lt;div class="main-content-end" aria-hidden="true"&gt;&lt;/div&gt;
&lt;h2 id="about-the-authors"&gt;About the authors&lt;/h2&gt;
&lt;p&gt;Florence Haudin is a scientific software developer at &lt;a href="https://twitter.com/QuantStack"&gt;QuantStack&lt;/a&gt;. She contributes to JupyterLab and to ipyleaflet.&lt;/p&gt;
&lt;p&gt;Frédéric Collonval supervised this work as a technical director at QuantStack. He is a member of the JupyterLab core team and authored several JupyterLab extensions.&lt;/p&gt;
</content><category term="accessibility"/><category term="extensions"/><category term="JupyterLab"/></entry><entry><title>Introducing Jupyter Scheduler</title><link href="https://blog.jupyter.org/posts/2022/introducing-jupyter-scheduler/" rel="alternate"/><published>2022-12-14T15:26:00+00:00</published><updated>2023-02-01T18:20:00+00:00</updated><author><name>Jason Weill</name></author><id>tag:blog.jupyter.org,2022-12-14:/posts/2022/introducing-jupyter-scheduler/</id><summary type="html">&lt;p&gt;The Open Source Jupyter team at AWS is proud to announce the release of Jupyter Scheduler, a JupyterLab extension that can run your Jupyter notebooks in the background. Jupyter Scheduler was developed from the start as an open-source project under the umbrella of the Jupyter project and governance.&lt;/p&gt;</summary><content type="html">&lt;p&gt;The Open Source Jupyter team at AWS is proud to announce the release of &lt;a href="https://github.com/jupyter-server/jupyter-scheduler/"&gt;Jupyter Scheduler&lt;/a&gt;, a JupyterLab extension that can run your Jupyter notebooks in the background. Jupyter Scheduler was developed from the start as an open-source project under the umbrella of the Jupyter project and governance. You can set up your notebooks to run once or on a schedule. By default, Jupyter Scheduler runs notebooks on your local system where Jupyter Server is installed; you can also extend the scheduler to run your notebooks in the cloud, on an on-premise cluster, on JupyterHub, or even on supercomputers.&lt;/p&gt;
&lt;p&gt;We hope you’ll find Jupyter Scheduler useful for running notebooks periodically, such as daily or weekly reports. You can install it in JupyterLab by running &lt;code&gt;pip install jupyter_scheduler&lt;/code&gt;, then restarting JupyterLab. You can create &lt;strong&gt;jobs&lt;/strong&gt; (single runs of an individual notebook) and &lt;strong&gt;job definitions&lt;/strong&gt; (scheduled recurring notebook jobs) in two ways. You can right-click on a notebook in the file browser and choose “Create Notebook Job” from the context menu, or you can use the new toolbar button in an open notebook file.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Screen shot of the file browser showing the context menu for a notebook" src="https://blog.jupyter.org/posts/2022/introducing-jupyter-scheduler/images/001-1_nUcvmd-_anAtX4rT5MGPfA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;When you right-click on a notebook file in the file browser, a “Create Notebook Job” option appears in the context menu.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img alt="Screen shot of a notebook showing a “Create Notebook Job” button in the toolbar" src="https://blog.jupyter.org/posts/2022/introducing-jupyter-scheduler/images/002-1_OOLLSTNnsaCLnX_g46Rq9A.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;In a notebook, the “Create Notebook Job” option appears on the right of the toolbar.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;You can give your notebook job or job definition a name, choose an environment to run it in, select different output formats, and provide parameters that are set as local variables when your notebook gets executed. This parameterized execution is similar to &lt;a href="https://papermill.readthedocs.io/en/latest/"&gt;Papermill&lt;/a&gt;; Jupyter Scheduler provides a UI for managing and accessing the outputs of your notebook jobs inside JupyterLab.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Screen shot of the Create Job form with sample input values provided" src="https://blog.jupyter.org/posts/2022/introducing-jupyter-scheduler/images/003-1_YGJD3HWvIenbouO7R8bJYg.jpeg" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;You can use the Create Job form to run a notebook once or to create a job definition that runs on a schedule.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In addition, if you select “Run on a schedule,” you can run your job on a recurring schedule. The scheduler provides a few useful shortcuts to, for example, run your notebook at a specified interval by the minute, hour, day or week. You can also specify a custom schedule in &lt;a href="https://www.man7.org/linux/man-pages/man5/crontab.5.html"&gt;crontab format&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Screen shot of the “run on a schedule” user interface in the Create Job form, with the “weekday” interval selected" src="https://blog.jupyter.org/posts/2022/introducing-jupyter-scheduler/images/004-1_TxAfPAOcW65yMfvmMiCi9A.jpeg" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;You can schedule a job definition using preset intervals, such as every weekday.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img alt="Screen shot of the “run on a schedule” user interface in the Create Job form, with the “custom schedule” interval selected" src="https://blog.jupyter.org/posts/2022/introducing-jupyter-scheduler/images/005-1_pBEzuVXkDtPiopR_5DxQsA.jpeg" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;You can also select “custom schedule” and specify a cron expression for the notebook job definition’s schedule.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Once you’ve created a job or job schedule, you can use the “Notebook Jobs” icon on the launcher to view lists of jobs and job definitions. Each item’s name links to a detail view.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Screen shot of JupyterLab launcher showing “Notebook Jobs” button in the “Other” section" src="https://blog.jupyter.org/posts/2022/introducing-jupyter-scheduler/images/006-1_Bwbm_RIsRiZz-zfCsv43Hg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;With the Jupyter Scheduler extension installed, JupyterLab’s launcher screen shows a “Notebook Jobs” button in the “Other” section. Click on it to view a list of jobs or job definitions.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img alt="Screen shot of the List Notebook Jobs view, showing many jobs, with links and information for each" src="https://blog.jupyter.org/posts/2022/introducing-jupyter-scheduler/images/007-1_RqogrC-XJlQLQhDJ-wml6A.jpeg" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;The list view shows a list of notebook jobs and a list of notebook job definitions. Each one includes links to view details, to download files, and buttons to take quick actions on them.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img alt="Screen shot of the Job Detail view, showing additional information and controls for a single job" src="https://blog.jupyter.org/posts/2022/introducing-jupyter-scheduler/images/008-1_BpShpinXD9ifmkfzeT30Cg.jpeg" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;The Job Detail view shows all available details for one job or one job definition.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When one of your jobs has completed, you can download its files from the list or detail view. When your notebook runs, the scheduler saves a copy of the input file, so that even if you later change the file, it captures the state of the notebook at the time your job ran. Jupyter Scheduler uses unique names for the input and output files so that rerunning the same notebook produces new files every time.&lt;/p&gt;
&lt;p&gt;Jupyter Scheduler uses a REST API to create, update, delete and manage jobs and job definitions. You can also customize Jupyter Scheduler by plugging in your own scheduler class, for example, to replace the existing SQLite database or to run jobs in the cloud. AWS SageMaker Studio actually uses Jupyter Scheduler, with custom classes, as part of its automated notebooks feature, &lt;a href="https://aws.amazon.com/blogs/aws/next-generation-sagemaker-notebooks-now-with-built-in-data-preparation-real-time-collaboration-and-notebook-automation/"&gt;announced&lt;/a&gt; earlier this year. See the &lt;a href="https://jupyter-scheduler.readthedocs.io/en/latest/operators/index.html#"&gt;Jupyter Scheduler operator documentation&lt;/a&gt; for more information about how to write and use your own scheduler.&lt;/p&gt;
&lt;p&gt;We’d love to hear what you think about Jupyter Scheduler! You can check out &lt;a href="https://jupyter-scheduler.readthedocs.io/"&gt;Jupyter Scheduler’s documentation on ReadTheDocs&lt;/a&gt;. Please &lt;a href="https://github.com/jupyter-server/jupyter-scheduler/issues"&gt;open issues&lt;/a&gt; with ideas for enhancements and about any bugs you find. If you’d like to contribute code, see the &lt;a href="https://jupyter-scheduler.readthedocs.io/en/latest/contributors/index.html"&gt;Jupyter Scheduler contributors guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This article does not represent the official position of amazon.com or any of its subsidiaries.&lt;/em&gt;&lt;/p&gt;
&lt;div class="main-content-end" aria-hidden="true"&gt;&lt;/div&gt;
&lt;h2 id="about-the-authors"&gt;About the authors&lt;/h2&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2022/introducing-jupyter-scheduler/images/009-1_7kFoTl09bhq0WbJbO2kFtw.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/andrii-i/"&gt;Andrii Ieroshenko&lt;/a&gt; is a Software Development Engineer at AWS. Previously, Andrii worked on the &lt;a href="https://github.com/jupytercalpoly/jupyterlab-notifications"&gt;Notifications&lt;/a&gt; project as a part of the Jupyter Cal Poly student internship program.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2022/introducing-jupyter-scheduler/images/010-1_3REz_2GLxridP9Uv6ZJjmQ.jpeg" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/3coins"&gt;Piyush Jain&lt;/a&gt; is a Software Development Engineer at AWS working on open source contributions to Project Jupyter. He is also a member of the Jupyter Server team, actively participating in maintenance and development of Jupyter server project.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2022/introducing-jupyter-scheduler/images/011-1_HrkANk8wyWRqXocQdnQ5mA.jpeg" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/dlqqq/"&gt;David Qiu&lt;/a&gt; is a Software Development Engineer at AWS working on Project Jupyter. Previously, David was researching physical chemistry at the University of Illinois at Urbana-Champaign.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2022/introducing-jupyter-scheduler/images/012-1_E40JRdqbPa7diRn6TXoCMQ.jpeg" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/JasonWeill"&gt;Jason Weill&lt;/a&gt; is a Senior Front-End Engineer at Amazon Web Services; a contributor to JupyterLab, Jupyter Governance, and Jupyter Scheduler; and a member of the JupyterLab Council, the Jupyter Security Working Group, and the Jupyter Diversity, Equity, and Inclusion Standing Committee.&lt;/p&gt;
</content><category term="extensions"/><category term="Jupyter Server"/><category term="JupyterLab"/></entry><entry><title>Upgrading Nbgrader</title><link href="https://blog.jupyter.org/posts/2022/upgrading-nbgrader/" rel="alternate"/><published>2022-09-15T07:51:00+00:00</published><updated>2022-09-15T08:09:00+00:00</updated><author><name>Nicolas Brichet</name></author><id>tag:blog.jupyter.org,2022-09-15:/posts/2022/upgrading-nbgrader/</id><summary type="html">&lt;p&gt;Project Jupyter provides a broad collection of open-source tools for interactive computing that has become ubiquitous in data science and scientific computing, and is very popular in educational contexts.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Project Jupyter&lt;/em&gt; provides a broad collection of open-source tools for interactive computing that has become ubiquitous in data science and scientific computing, and is very popular in educational contexts.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://nbgrader.readthedocs.io/en/stable/"&gt;&lt;em&gt;Nbgrader&lt;/em&gt;&lt;/a&gt;, started by &lt;a href="https://twitter.com/jhamrick"&gt;Jessica Hamrick&lt;/a&gt; in 2014, is an extension for creating notebook-based assignments, online courses, and for auto-grading these assignments. It is central to many Jupyter-based services used by teaching institutions.&lt;/p&gt;
&lt;p&gt;As a large part of the Jupyter user base has adopted the JupyterLab frontend, most of the popular extensions to the classic notebook UI now have an equivalent in the JupyterLab world. Unfortunately, the nbgrader extension was still outstanding as it had not yet been ported to the JupyterLab extension system. Resolving this has become critical as it was recently decided in a &lt;a href="https://github.com/jupyter/enhancement-proposals/blob/master/79-notebook-v7/notebook-v7.md"&gt;Jupyter Enhancement Proposal&lt;/a&gt; (JEP) that the next major version of the Notebook package (v7) will be based on JupyterLab components, and will rely on the JupyterLab extension mechanism. As part of this JEP, it was decided that the release of Notebook 7 should be contingent on porting certain popular classic notebook extensions to JupyterLab, including nbgrader.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Upgrading nbgrader is an important step for the advancement of the Jupyter project!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="nbgrader-08"&gt;Nbgrader 0.8&lt;/h2&gt;
&lt;p&gt;The port of nbgrader to JupyterLab is a longstanding request of the community. A lot of work had already been done on this front by contributors to the &lt;a href="https://ngshare.readthedocs.io/en/latest/"&gt;ngshare&lt;/a&gt; project (Kevin Rong, Abigail Almanza, Lawrence Lee, and Eric Li) who developed a large part of the JupyterLab-based UI, but did not finalize the migration due to lack of time. Fortunately, they made their work available to the community for others to complete the upgrade. This includes the development of the remaining elements of the UI, the adaptation of the server-side extension, and adding tests on the JupyterLab extensions to improve the CI. The nbgrader 0.8 release also includes other significant work items such as the upgrade to nbconvert 6, the switch from the notebook server to jupyter-server, and the adoption of the jupyter releaser.&lt;/p&gt;
&lt;p&gt;Today, we are pleased to announce the release of nbgrader 0.8.0, the first release of the extension to support the JupyterLab frontend.&lt;/p&gt;
&lt;h3 id="creating-an-assignment"&gt;Creating an assignment&lt;/h3&gt;
&lt;p&gt;Teachers can create assignments directly in the notebook UI by setting the proper information for each cell, such as the cell type (whether it is a locked cell, a manually graded answer, or an automatically graded answer) and the number of points the answer is worth.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Screencast, of the UI for creating an assignment in the notebook (teachers)." src="https://blog.jupyter.org/posts/2022/upgrading-nbgrader/images/001-1_apqtMnPMoBql8XXbIQOpYA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;The UI for creating an assignment in the notebook (teachers).&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="managing-assignments-on-the-teacher-side"&gt;Managing assignments on the teacher side&lt;/h3&gt;
&lt;p&gt;Formgrader is the part of the nbgrader UI for the teachers to manage the assignments.&lt;/p&gt;
&lt;p&gt;From this UI, teachers will be able to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;generate assignments from a Notebook file&lt;/li&gt;
&lt;li&gt;share assignments with students&lt;/li&gt;
&lt;li&gt;fetch assignments after student submission&lt;/li&gt;
&lt;li&gt;grade assignments&lt;/li&gt;
&lt;li&gt;provide feedback to students&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
&lt;img alt="Formgrader UI" src="https://blog.jupyter.org/posts/2022/upgrading-nbgrader/images/002-1_S2B9CFCZi4fLA-spXbqhzg.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;The Formgrader UI to manage assignments (teachers).&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="managing-assignments-on-the-student-side"&gt;Managing assignments on the student side&lt;/h3&gt;
&lt;p&gt;The assignments list UI is used by students to fetch assignments released by the teacher, submit their answers, and benefit from the feedback after the evaluation by teachers.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="assignments list UI" src="https://blog.jupyter.org/posts/2022/upgrading-nbgrader/images/003-1_iDhidg4ebeIL2a6pYJOOvw.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;The assignments list UI to fetch and submit assignments (students).&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="what-about-the-future"&gt;What about the future?&lt;/h2&gt;
&lt;p&gt;Creating and grading assignments in nbgrader uses the metadata of individual notebook cells. The current release of nbgrader provides a widget to interact with the corresponding metadata.&lt;/p&gt;
&lt;p&gt;As other extensions need a similar functionality, we are expanding the generic cell metadata editor to address this requirement in a more generic and pluggable way. The new metadata editor allows any extension to create a specific form to interact with the relevant part of the metadata, specified with a JSON schema and rendered with &lt;a href="https://react-jsonschema-form.readthedocs.io/en/latest/"&gt;react-jsonschema-form&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Nbgrader could take advantage of this improvement in a future release.&lt;/p&gt;
&lt;h2 id="try-it-online"&gt;Try it online&lt;/h2&gt;
&lt;p class="standalone-image"&gt;&lt;a href="https://mybinder.org/v2/gh/jupyter/nbgrader.git/v0.8.0"&gt;&lt;img src="https://blog.jupyter.org/posts/2022/upgrading-nbgrader/images/004-1_KlCHlIFqOeXmqzGmwlh52w.webp" alt="binder" loading="lazy" data-body-image=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="installation"&gt;Installation&lt;/h2&gt;
&lt;p&gt;You can install the latest version of nbgrader using pip:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install nbgrader
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The GitHub repository can be found &lt;a href="https://github.com/jupyter/nbgrader/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;div class="main-content-end" aria-hidden="true"&gt;&lt;/div&gt;
&lt;h2 id="acknowledgements"&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;This work at &lt;a href="https://twitter.com/QuantStack"&gt;QuantStack&lt;/a&gt; was partly funded by &lt;a href="https://u-paris.fr/"&gt;Université Paris Cité&lt;/a&gt; in the context of the &lt;a href="http://plasmabio.org"&gt;PlasmaBio&lt;/a&gt; project.&lt;/p&gt;
&lt;p&gt;We are grateful to &lt;a href="https://twitter.com/jhamrick"&gt;Jessica Hamrick&lt;/a&gt;, &lt;a href="https://twitter.com/jtpio"&gt;Jeremy Tuloup&lt;/a&gt;, and Ian Stuart for their help and code reviews during the upgrade to nbgrader 0.8.0.&lt;/p&gt;
&lt;h2 id="about-the-author"&gt;About the Author&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://twitter.com/NicolasBrichet_"&gt;Nicolas Brichet&lt;/a&gt; is a scientific software developer at &lt;a href="https://twitter.com/QuantStack"&gt;QuantStack&lt;/a&gt; active in the Jupyter ecosystem. In the past few months, he has been working on the Nbgrader extension, as well as other developments in core JupyterLab such as an improved notebook metadata editor. Before joining QuantStack, Nicolas was a software engineer at INRAE.&lt;/p&gt;
</content><category term="education"/><category term="extensions"/><category term="JupyterLab"/><category term="releases"/></entry><entry><title>Packaging for Jupyter in 2022</title><link href="https://blog.jupyter.org/posts/2022/packaging-for-jupyter-in-2022/" rel="alternate"/><published>2022-09-06T15:15:00+00:00</published><updated>2022-09-06T18:56:00+00:00</updated><author><name>Steven Silvester</name></author><id>tag:blog.jupyter.org,2022-09-06:/posts/2022/packaging-for-jupyter-in-2022/</id><summary type="html">&lt;p&gt;Packaging has been difficult and changing over time in the Python ecosystem, and packaging extensions for Jupyter has been especially troublesome.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Packaging has been difficult and changing over time in the Python ecosystem, and packaging extensions for Jupyter has been especially troublesome. Jupyter extensions typically have a build step to handle JavaScript assets, and provide Jupyter configuration and data using the data_files capability in Setuptools. If you’ve ever developed your own Jupyter extension before, you understand the pain all too well.&lt;/p&gt;
&lt;p&gt;We’ve tried to help over the years. We created the &lt;a href="https://github.com/jupyter/jupyter-packaging"&gt;Jupyter Packaging&lt;/a&gt; library, with helper functions and Setuptools builder classes. We have also created cookiecutter repositories for &lt;a href="https://github.com/jupyterlab/extension-cookiecutter-ts"&gt;JupyterLab&lt;/a&gt;, &lt;a href="https://github.com/jupyter-widgets/widget-ts-cookiecutter"&gt;Widget&lt;/a&gt;, and &lt;a href="https://github.com/jupyter-server/extension-cookiecutter"&gt;Server&lt;/a&gt; extensions. Additionally, there have been a number of libraries and Python Enhancement Proposals produced by the &lt;a href="https://www.pypa.io/en/latest/index.html"&gt;Python Packaging Authority&lt;/a&gt;, aiming to standardize and improve the Python Packaging ecosystem. Even with all these improvements, packaging Jupyter libraries remained a challenge.&lt;/p&gt;
&lt;p&gt;Then we discovered the &lt;a href="https://hatch.pypa.io/latest/"&gt;Hatch&lt;/a&gt; project.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Logo for the Hatch Python Project" src="https://blog.jupyter.org/posts/2022/packaging-for-jupyter-in-2022/images/001-1_SL9lt02en8mWEJylo9sFhw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;“Hatch is a modern, extensible Python project manager.”&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Hatch is a standards-based Python packaging backend that is flexible and extensible. Hatch came with many of the things we needed. It provides a supported replacement for Setuptool’s (deprecated) data_files called &lt;a href="https://hatch.pypa.io/latest/plugins/builder/wheel/#options"&gt;&lt;em&gt;shared-data&lt;/em&gt;&lt;/a&gt;. We could easily extend Hatch to handle the JavaScript build step required for most of our applications and extensions with a new plugin called &lt;a href="https://github.com/jupyterlab/hatch-jupyter-builder"&gt;Hatch Jupyter Builder&lt;/a&gt; that is tailored for Jupyter extensions. The best news? No more complicated &lt;em&gt;setup.py&lt;/em&gt; files in your package. Just a single, standards-driven &lt;em&gt;pyproject.toml&lt;/em&gt; in your root directory describing all of your package details.&lt;/p&gt;
&lt;p&gt;We believe this is a major improvement for Jupyter developers and extension authors.&lt;/p&gt;
&lt;p&gt;For Jupyter extension authors that previously used one of our cookiecutter templates or Jupyter Packaging directly, we offer a &lt;a href="https://hatch-jupyter-builder.readthedocs.io/en/stable/source/how_to_guides/migrating_javascript_projects.html"&gt;migration&lt;/a&gt; script and guides to convert the Setuptools and Jupyter Packaging configuration to use Hatch and Hatch Jupyter Builder. We also provide a script that will compare the files in an &lt;em&gt;sdist&lt;/em&gt; or &lt;em&gt;wheel&lt;/em&gt; before and after migration to make sure you do not accidentally add or subtract files.&lt;/p&gt;
&lt;p&gt;We have already been migrating many of the core &lt;a href="https://github.com/jupyter-server/jupyter_server/pull/845"&gt;Jupyter&lt;/a&gt; and &lt;a href="https://github.com/ipython/ipykernel/pull/931"&gt;IPython&lt;/a&gt; libraries to use the Hatch build backend, using Hatch Jupyter Builder where appropriate. We have upgraded several of our cookiecutter templates to use Hatch as well. The transition has been quite smooth!&lt;/p&gt;
&lt;p&gt;We highly encourage you to try out Hatch and Hatch Jupyter Builder for existing or new Python libraries.&lt;/p&gt;
&lt;div class="main-content-end" aria-hidden="true"&gt;&lt;/div&gt;
&lt;h2 id="about-the-authors"&gt;About the authors&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Steven Silvester&lt;/strong&gt; is a Senior Python Engineer at MongoDB working on the Database Experience Team. He is a Jupyter Steering Council Member and contributes to the maintenance of many of the Jupyter projects.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Zach Sailer&lt;/strong&gt; is a Senior Software Engineer at Apple, where he is the Jupyter Open Source Champion. He is a Jupyter Distinguished Contributor and leader of the Jupyter Server subproject.&lt;/p&gt;
</content><category term="extensions"/></entry><entry><title>Visual programming in JupyterLab with Blockly</title><link href="https://blog.jupyter.org/posts/2022/visual-programming-in-jupyterlab-with-blockly/" rel="alternate"/><published>2022-07-29T13:12:00+00:00</published><updated>2022-07-29T13:12:00+00:00</updated><author><name>Denisa Checiu</name></author><id>tag:blog.jupyter.org,2022-07-29:/posts/2022/visual-programming-in-jupyterlab-with-blockly/</id><summary type="html">&lt;p&gt;Block-based programming has become ubiquitous in school curricula for early computer science education. It is an excellent means to teach basic programming concepts without having to deal with the specific syntax of a text-based programming language.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Block-based programming has become ubiquitous in school curricula for early computer science education. It is an excellent means to teach basic programming concepts without having to deal with the specific syntax of a text-based programming language.&lt;/p&gt;
&lt;p&gt;When moving from block-based programming to a more classical language like Python, students often have to switch to a completely new environment. In order to provide a smooth ramp of complexity for learners, we designed a JupyterLab extension for Blockly so that Jupyter can be used from the very first steps of their learning journey. We also used Blockly’s code generation feature to create a more integrated experience with Jupyter.&lt;/p&gt;
&lt;h2 id="but-what-exactly-is-blockly"&gt;But what exactly is Blockly?&lt;/h2&gt;
&lt;figure&gt;
&lt;img alt="Screenshot of the JupyterLab-Blockly extension, running a simple code" src="https://blog.jupyter.org/posts/2022/visual-programming-in-jupyterlab-with-blockly/images/001-0_yEQ8djozBKDLcCxZ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;The JupyterLab-Blockly extension, running a simple loop, and the corresponding Python code in a cell.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a href="https://developers.google.com/blockly"&gt;Blockly&lt;/a&gt; is an open source library designed by &lt;a href="https://twitter.com/GoogleOSS"&gt;Google&lt;/a&gt; to make coding easier and more accessible through block-based visual programming. It uses interlocking graphical blocks to represent coding concepts, while giving the user full creative freedom, it removes all language specific syntax requirements.&lt;/p&gt;
&lt;h2 id="getting-started-installing-the-blockly-extension"&gt;Getting started: installing the Blockly extension&lt;/h2&gt;
&lt;p&gt;To install the extension, you just need to execute the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mamba install -c conda-forge jupyterlab-blockly
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once installed, you can easily open a Blockly editor directly from the launcher, and start experimenting!&lt;/p&gt;
&lt;h2 id="features"&gt;Features&lt;/h2&gt;
&lt;p&gt;The UI is made primarily of the toolbox located on the left side and the canvas where you can drag and drop the blocks you want to use in your code. When you are ready, just click the run button and see how the syntactically correct converted code appears on the bottom part of your screen in the programming language of your choice along with the output!&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Short demo on how to use the JupyterLab-Blockly extension" src="https://blog.jupyter.org/posts/2022/visual-programming-in-jupyterlab-with-blockly/images/002-0_69P-HcYWrtriGmS-.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Short demo for the JupyterLab-Blockly extension&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The extension is also prepared to adapt to all your needs. The workspace is ready to generate the blocks using your selected JupyterLab kernel and modify its colors based on your individual theme, whether it’s dark, light or personalized.&lt;/p&gt;
&lt;p&gt;JupyterLab-Blockly integrates smoothly with the rest of the Jupyter stack: by using Jupyter kernels to execute the generated code, by reusing the JupyterLab code cell component to display the generated code, etc.&lt;/p&gt;
&lt;p&gt;In doing so, JupyterLab-Blockly remains consistent with the rest of the UI (consistent syntax highlighting, theming, and life cycle of Jupyter kernels).&lt;/p&gt;
&lt;h2 id="robotics-education"&gt;Robotics education&lt;/h2&gt;
&lt;p&gt;On top of JupyterLab-Blockly, we also built another extension dedicated to operating robots with the help of visual programming — more specifically, we created a toolbox which includes the blocks necessary to control the &lt;a href="https://niryo.com/fr/product/niryo-one/"&gt;Niryo One&lt;/a&gt; robot. Using ROS can be a time consuming task, but the pre-built visual blocks make it easier to experiment and faster to produce a prototype for any project you have in mind.&lt;/p&gt;
&lt;p&gt;To install this extension you need to execute the following commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mamba install -c conda-forge jupyterlab-niryo-one
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then, all you need to do is switch from the &lt;em&gt;‘default’&lt;/em&gt; toolbox to the &lt;em&gt;‘niryo’&lt;/em&gt; one, and you will immediately gain access to forty-two new blocks, so if you have access to one of these robots, give it a try!&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Demo on how to use the JupyterLab-NiryoOne extension" src="https://blog.jupyter.org/posts/2022/visual-programming-in-jupyterlab-with-blockly/images/003-0_KSNrd_DXpVWHkvPC.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Short demo for the JupyterLab-NiryoOne extension&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Both extensions can be found on GitHub. The &lt;a href="https://github.com/QuantStack/jupyterlab-blockly"&gt;JupyterLab-Blockly&lt;/a&gt; extension is ready to be used as a base for other projects; you can register new blocks, toolboxes and generators. Taking the &lt;a href="https://github.com/QuantStack/jupyterlab_niryo_one"&gt;JupyterLab-Niryo-One&lt;/a&gt; extension as an example, feel free to experiment on your own!&lt;/p&gt;
&lt;figure&gt;
&lt;iframe src="https://www.youtube-nocookie.com/embed/41GoTIljCug" title="Demo JupyterLab-Niryo-One with Blockly" width="560" height="315" style="aspect-ratio: 560 / 315" loading="lazy" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;
&lt;figcaption&gt;
&lt;p&gt;Controlling the Niryo-one robot with the JupyterLab-blockly extension.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;You can also try out the JupyterLab-Blockly extension now, directly from your browser, using JupyterLite! Just click on the &lt;a href="https://jupyterlab-blockly.readthedocs.io/en/latest/lite/lab/index.html?path=example.jpblockly"&gt;link&lt;/a&gt; below.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;a href="https://jupyterlab-blockly.readthedocs.io/en/latest/lite/lab/index.html?path=example.jpblockly"&gt;&lt;img src="https://blog.jupyter.org/posts/2022/visual-programming-in-jupyterlab-with-blockly/images/004-0_Z2TP8VlU-j14Taxo.webp" alt="Link to JupyterLite for the JupyterLab-Blockly extension" loading="lazy" data-body-image=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="main-content-end" aria-hidden="true"&gt;&lt;/div&gt;
&lt;h2 id="about-the-authors"&gt;About the authors&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://twitter.com/DenisaCheciu"&gt;&lt;strong&gt;Denisa Checiu&lt;/strong&gt;&lt;/a&gt; is a Robotics working student at &lt;a href="https://twitter.com/QuantStack"&gt;QuantStack&lt;/a&gt;. She is also currently pursuing her bachelor’s degree, majoring in Robotics and Intelligent Systems and minoring in Computer Science.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://twitter.com/carlosherrerob"&gt;&lt;strong&gt;Carlos Herrero&lt;/strong&gt;&lt;/a&gt; is a scientific software developer at &lt;a href="https://twitter.com/QuantStack"&gt;QuantStack&lt;/a&gt; passionate about AI and its applications to robotics. His current work focuses on the JupyterLab stack and expanding the &lt;a href="https://jupyter.org/"&gt;Jupyter ecosystem&lt;/a&gt; by developing new extensions to integrate &lt;a href="https://www.ros.org/"&gt;ROS&lt;/a&gt; in JupyterLab and also contributing to &lt;a href="https://github.com/voila-dashboards/voila"&gt;Voilà&lt;/a&gt;.&lt;/p&gt;
</content><category term="education"/><category term="extensions"/><category term="JupyterLab"/></entry><entry><title>Dashboarding with JupyterLab 3</title><link href="https://blog.jupyter.org/posts/2021/dashboarding-with-jupyterlab-3/" rel="alternate"/><published>2021-01-08T10:25:00+00:00</published><updated>2021-01-08T11:15:00+00:00</updated><author><name>Carlos Herrero</name></author><id>tag:blog.jupyter.org,2021-01-08:/posts/2021/dashboarding-with-jupyterlab-3/</id><summary type="html">&lt;p&gt;Project Jupyter offers a complete suite of open-source tools for the scientific computing community, reaching from the exploratory phase of a project to the presentation of the results.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Project Jupyter offers a complete suite of open-source tools for the scientific computing community, reaching from the exploratory phase of a project to the presentation of the results. In this last but not least phase is where Voilà takes place, &lt;strong&gt;turning a technical document such as Notebook into an interactive presentation or a stand-alone web app ready to be shared with non-technical readers&lt;/strong&gt;. You can learn more about Voilà in &lt;a href="https://blog.jupyter.org/posts/2019/and-voila/"&gt;this blog post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Voilà is an easy-to-use command-line tool that shines in its simplicity and flexibility. All its versatility comes from a custom template system built upon Jinja templates that allows everyone to create a custom UI to share Jupyter Notebooks. Examples of the versatility provided for the template system are &lt;a href="https://blog.jupyter.org/posts/2019/a-slideshow-template-for-voila-apps/"&gt;&lt;em&gt;voila-reveal&lt;/em&gt;&lt;/a&gt; and &lt;a href="https://blog.jupyter.org/posts/2019/voila-gridstack-template/"&gt;&lt;em&gt;voila-gridstack&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Voila-gridstack&lt;/em&gt; is a Voilà template started by &lt;a href="https://github.com/btel"&gt;Bartosz Telenczuk&lt;/a&gt; to &lt;strong&gt;turn notebooks into dashboards&lt;/strong&gt; following the specification introduced by the legacy &lt;em&gt;jupyter-dashboards&lt;/em&gt; project. The idea behind is to be able to change the layout of the cells to re-configure your dashboards using drag-and-drop. Once you have your desired layout, its configuration stays in the metadata of the notebook. This makes it simple to carry around or share the notebook and its layout configuration.&lt;/p&gt;
&lt;p&gt;With the release of JupyterLab 3.0, we wanted to update the &lt;em&gt;voila-gridstack&lt;/em&gt; template by adding an editor for JupyterLab.&lt;/p&gt;
&lt;h2 id="configure-your-layout"&gt;Configure your layout&lt;/h2&gt;
&lt;p&gt;The first way to open the &lt;em&gt;voila-gridstack&lt;/em&gt; editor is by right-clicking on the notebook and selecting open with “Voilà Gridstack”. The editor is itself a JupyterLab Document Widget, which can be seen as another view of the same notebook document. The editor can also be launched from the classic notebook with a toolbar button.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Opening JupyterLab-Gridstack" src="https://blog.jupyter.org/posts/2021/dashboarding-with-jupyterlab-3/images/001-1_m3zuJtMenNyGL-0R2nz7Cg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Opening JupyterLab-Gridstack&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Once the editor is open, every cell is decorated with a toolbar as a drag-and-drop handle and a button to remove the cell from the dashboard. When removing a cell, it is just marked as hidden in the metadata but it is not deleted from the notebook file. Besides, handles are provided for resizing cells in the layout.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Configuring your layout" src="https://blog.jupyter.org/posts/2021/dashboarding-with-jupyterlab-3/images/002-1_u0AnafiS2HtXhCiCMg25cA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Configuring your layout&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When opening the editor for the first time in a new notebook, the metadata of the notebook is modified to follow &lt;em&gt;jupyter-dashboards&lt;/em&gt;’ specification. By default, you will see an empty editor. To add new cells just drag them from the notebook view. It is not possible to add cells from another notebook.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Adding new cells" src="https://blog.jupyter.org/posts/2021/dashboarding-with-jupyterlab-3/images/003-1_t4EW3oCIrt5FaMXUyjYPFw.gif" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Adding new cells&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Finally, you can save the layout into the metadata of the notebook by clicking the “save” button or with the &lt;code&gt;ctrl+s&lt;/code&gt; shortcut and then it’s time to hit the render button and Voilà! You will see your layout as a stand-alone web app.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Voilà! Your stand-alone web app" src="https://blog.jupyter.org/posts/2021/dashboarding-with-jupyterlab-3/images/004-1_y6-MX2tjbBavUr3GHFDnIw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Voilà! Your stand-alone web app&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="try-it-online"&gt;Try it online!&lt;/h2&gt;
&lt;p&gt;You can try the &lt;a href="https://github.com/voila-dashboards/voila-gridstack"&gt;&lt;em&gt;jupyterlab-gridstack&lt;/em&gt;&lt;/a&gt; extension online without installing anything by just clicking on the following image:&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;a href="https://mybinder.org/v2/gh/voila-dashboards/voila-gridstack/stable?urlpath=lab/tree/examples"&gt;&lt;img src="https://blog.jupyter.org/posts/2021/dashboarding-with-jupyterlab-3/images/005-1_JsZ-IeoC7225JzNIHR1acA.webp" alt="Launch Binder" loading="lazy" data-body-image=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="installation"&gt;Installation&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Voila-gridstack&lt;/em&gt; is available as a conda package, and you can easily install it by typing the following command in a conda environment:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mamba install -c conda-forge jupyterlab voila-gridstack
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="main-content-end" aria-hidden="true"&gt;&lt;/div&gt;
&lt;h2 id="acknowledgements"&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;The development of Voilà and related packages at &lt;a href="https://twitter.com/QuantStack"&gt;QuantStack&lt;/a&gt; is sponsored by &lt;a href="http://www.techatbloomberg.com"&gt;&lt;strong&gt;Bloomberg&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The example notebook used as an example in this post was one of the example notebooks of the legacy Jupyter dashboards project.&lt;/p&gt;
&lt;h2 id="about-the-authors"&gt;About the authors&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://twitter.com/CarlosHerreroB"&gt;Carlos Herrero&lt;/a&gt; is a Computer Engineer passionate about AI and its applications to robotics. Currently working at QuantStack helping to develop Open Source projects.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Carlos Herrero" src="https://blog.jupyter.org/posts/2021/dashboarding-with-jupyterlab-3/images/006-1_y3NheY8vpXLUDnh7Cpnruw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Carlos Herrero&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a href="https://twitter.com/jtpio"&gt;Jeremy Tuloup&lt;/a&gt; is a Scientific Software Developer at QuantStack, working on JupyterLab, Voilà, and various other projects within the Jupyter ecosystem.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Jeremy Tuloup" src="https://blog.jupyter.org/posts/2021/dashboarding-with-jupyterlab-3/images/007-1_TSPSpj2D0UODcXXAgbHOXg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Jeremy Tuloup&lt;/figcaption&gt;
&lt;/figure&gt;
</content><category term="dashboards"/><category term="extensions"/><category term="JupyterLab"/><category term="Voilà"/></entry><entry><title>JupyterLab 3.0 is released!</title><link href="https://blog.jupyter.org/posts/2021/jupyterlab-3-0-is-out/" rel="alternate"/><published>2021-01-05T08:28:00+00:00</published><updated>2021-07-19T16:35:00+00:00</updated><author><name>Jeremy Tuloup</name></author><id>tag:blog.jupyter.org,2021-01-05:/posts/2021/jupyterlab-3-0-is-out/</id><summary type="html">&lt;p&gt;The 3.0 release of JupyterLab brings many new features to users and substantial improvements to the extension system.&lt;/p&gt;</summary><content type="html">&lt;p&gt;The 3.0 release of JupyterLab brings many new features to users and substantial improvements to the extension system.&lt;/p&gt;
&lt;h2 id="installation"&gt;Installation&lt;/h2&gt;
&lt;p&gt;To install JupyterLab with &lt;code&gt;pip&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install jupyterlab==3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With &lt;a href="https://github.com/mamba-org/mamba"&gt;&lt;code&gt;mamba&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mamba install -c conda-forge jupyterlab=3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With &lt;a href="https://docs.conda.io/"&gt;&lt;code&gt;conda&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;conda install -c conda-forge jupyterlab=3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(Note that many third-party extensions are still in the process of updating to be compatible with JupyterLab 3.0 — please check the extensions you use and be patient, or even better, help them update! Also, it may help to uninstall JupyterLab 2 if you already have it installed.)&lt;/p&gt;
&lt;p&gt;Here is an overview of the major user-facing changes in JupyterLab 3.0.&lt;/p&gt;
&lt;h2 id="debugger"&gt;Debugger&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://blog.jupyter.org/posts/2020/a-visual-debugger-for-jupyter/"&gt;Announced earlier this year&lt;/a&gt;, the visual debugger is now shipped by default with JupyterLab.&lt;/p&gt;
&lt;p&gt;To use the visual debugger, you will need a kernel that supports debugging. An example of such kernel is &lt;a href="https://xeus-python.readthedocs.io"&gt;&lt;code&gt;xeus-python&lt;/code&gt;&lt;/a&gt;, the first Jupyter Kernel for Python with support for debugging.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Stepping into Python code in JupyterLab with the visual debugger" src="https://blog.jupyter.org/posts/2021/jupyterlab-3-0-is-out/images/001-1_RoJnb1XpB6g5geG15WgrMg.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Stepping into Python code in JupyterLab with the visual debugger&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Check out the &lt;a href="https://jupyterlab.readthedocs.io/en/stable/user/debugger.html"&gt;user documentation&lt;/a&gt; for more information, including a notebook based tutorial to interactively learn how to use the debugger.&lt;/p&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;/h2&gt;
&lt;p&gt;The table of contents extension now ships with JupyterLab. This makes it easy to see and navigate the structure of a document.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Using the Table of Content in JupyterLab 3.0" src="https://blog.jupyter.org/posts/2021/jupyterlab-3-0-is-out/images/002-1_bSH9IcyWHLaNML2q9lRThg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Using the Table of Content in JupyterLab 3.0&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="support-for-multiple-display-languages"&gt;Support for multiple display languages&lt;/h2&gt;
&lt;p&gt;JupyterLab now provides the ability to set the display language of the user interface.&lt;/p&gt;
&lt;p&gt;Users will need to install the language pack as a separate Python package. Language packs are grouped in the &lt;a href="https://github.com/jupyterlab/language-packs/"&gt;language packs repository on GitHub&lt;/a&gt;, and can be installed with &lt;code&gt;pip&lt;/code&gt;. For example, it is possible to install the language pack for Simplified Chinese using the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install jupyterlab-language-pack-zh-CN
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figure&gt;
&lt;img alt="JupyterLab in Simplified Chinese" src="https://blog.jupyter.org/posts/2021/jupyterlab-3-0-is-out/images/003-0_8CYAXfBDt0v95iOc.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;JupyterLab in Simplified Chinese&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Check out the &lt;a href="https://jupyterlab.readthedocs.io/en/stable/user/language.html"&gt;user documentation&lt;/a&gt; to learn more about the feature and how to add new language packs.&lt;/p&gt;
&lt;h2 id="improvements-to-the-simple-interface-mode"&gt;Improvements to the Simple Interface mode&lt;/h2&gt;
&lt;p&gt;The Simple Interface mode (previously known as the Single-Document Mode) has been significantly updated to have a more streamlined, document-oriented feel.&lt;/p&gt;
&lt;p&gt;To make the Simple Interface mode more discoverable, there is now a switch to toggle it in the status bar. You can also toggle it from the View menu or the command palette, or use the default keyboard shortcut Ctrl/Cmd+Shift+D.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Enabling and disabling the Simple Interface Mode" src="https://blog.jupyter.org/posts/2021/jupyterlab-3-0-is-out/images/004-1_ZLDBAmTHYyGVp3RNQN09KQ.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Enabling and disabling the Simple Interface Mode&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Support for mobile has also greatly improved with a more compact layout. JupyterLab automatically switches to the Simple Interface when the window is resized down.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="JupyterLab switches to the Simple Interface Mode on smaller screen" src="https://blog.jupyter.org/posts/2021/jupyterlab-3-0-is-out/images/005-1_K24B_sxJ6Y-PHeLTURyRTw.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;JupyterLab switches to the Simple Interface Mode on smaller screen&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;There is &lt;a href="https://github.com/jupyterlab/jupyterlab/pull/9464"&gt;ongoing work&lt;/a&gt; to iterate on this concept and make the interface even more accessible on mobile devices.&lt;/p&gt;
&lt;h2 id="install-new-extensions-with-pip-and-conda-mamba"&gt;Install new extensions with pip and conda / mamba&lt;/h2&gt;
&lt;p&gt;JupyterLab extensions can now be distributed as &lt;em&gt;prebuilt extensions&lt;/em&gt;, which do not require a user to rebuild JupyterLab or have &lt;a href="https://nodejs.org"&gt;Node.js&lt;/a&gt; installed. Prebuilt extensions can be distributed as Python packages using familiar package managers such as &lt;code&gt;pip&lt;/code&gt;, &lt;code&gt;conda&lt;/code&gt;, and &lt;code&gt;mamba&lt;/code&gt;. This makes installing and using extensions much faster and more convenient!&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Installing a new extension with pip" src="https://blog.jupyter.org/posts/2021/jupyterlab-3-0-is-out/images/006-1_DXwxXMj-B1Jqpsx3RmUitA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Installing a new extension with pip&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Prebuilt extensions can be published to &lt;a href="https://pypi.org/"&gt;PyPI&lt;/a&gt; and &lt;a href="https://conda-forge.org/"&gt;conda-forge&lt;/a&gt; as separate packages, or bundled into existing packages with Jupyter Server extensions and Classic Notebook extensions. This helps make the ecosystem as a whole more coherent.&lt;/p&gt;
&lt;p&gt;For example, install the new &lt;code&gt;ipywidgets&lt;/code&gt; 7.6.0 with pip or conda to automatically enable ipywidgets in both the classic Jupyter Notebook and in JupyterLab 3.0 — no extra installation step or JupyterLab rebuild needed!&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Installing ipywidgets works automagically in JupyterLab 3.0!" src="https://blog.jupyter.org/posts/2021/jupyterlab-3-0-is-out/images/007-1_jg5drA_J783_IrqdLzye5w.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Installing ipywidgets works automagically in JupyterLab 3.0!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="improved-development-workflow-for-extension-authors"&gt;Improved development workflow for Extension Authors&lt;/h2&gt;
&lt;p&gt;The new prebuilt extensions are more convenient for extension authors to develop. The &lt;a href="https://github.com/jupyterlab/extension-cookiecutter-ts"&gt;TypeScript extension cookiecutter&lt;/a&gt; has been updated to develop prebuilt extensions by default and provides all the necessary tooling to quickly create a new extension from scratch.&lt;/p&gt;
&lt;p&gt;For more information, check out the &lt;a href="https://jupyterlab.readthedocs.io/en/stable/extension/extension_dev.html"&gt;Extension Developer Guide&lt;/a&gt; and the &lt;a href="https://jupyterlab.readthedocs.io/en/latest/extension/extension_migration.html"&gt;JupyterLab 2.x to 3.x Migration Guide&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="extension-examples"&gt;Extension Examples&lt;/h3&gt;
&lt;p&gt;If you are looking for examples to learn how to make your own extension, make sure to check out the &lt;a href="https://github.com/jupyterlab/extension-examples"&gt;extension examples repository on GitHub&lt;/a&gt;. The examples have been updated to be compatible with JupyterLab 3.0 and provide a hands-on approach to developing extensions.&lt;/p&gt;
&lt;h2 id="changelog"&gt;Changelog&lt;/h2&gt;
&lt;p&gt;This post was only an overview of what’s new in 3.0! If you are looking for a more complete list of the changes including bug fixes, be sure to have a look at the &lt;a href="https://jupyterlab.readthedocs.io/en/stable/getting_started/changelog.html#v3-0"&gt;changelog&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="try-it-out-on-binder"&gt;Try it out on Binder&lt;/h2&gt;
&lt;p&gt;JupyterLab 3.0 can easily be tested on Binder using the following link:&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;a href="https://mybinder.org/v2/gh/jupyterlab/jupyterlab-demo/master?urlpath=lab"&gt;&lt;img src="https://blog.jupyter.org/posts/2021/jupyterlab-3-0-is-out/images/008-1_fDQBE4twamqnpxvAevzGuw.webp" alt="Launch Binder" loading="lazy" data-body-image=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="get-involved"&gt;Get Involved&lt;/h2&gt;
&lt;p&gt;There are many ways you can participate in the JupyterLab effort. We welcome contributions from all members of the Jupyter community:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://jupyterlab.readthedocs.io/en/stable/extension/extension_dev.html"&gt;Make your own JupyterLab extensions&lt;/a&gt;. Please add the &lt;a href="http://github.com/topics/jupyterlab-extension"&gt;jupyterlab-extension&lt;/a&gt; topic if your extension is hosted on GitHub. You can also help existing extensions update to JupyterLab 3.0. The &lt;a href="https://jupyterlab.readthedocs.io/en/stable/getting_started/changelog.html#for-developers"&gt;changelog&lt;/a&gt; gives an overview of changes that may affect extension maintainers.&lt;/li&gt;
&lt;li&gt;Contribute to the development, documentation, and design of JupyterLab on &lt;a href="https://github.com/jupyterlab/jupyterlab"&gt;GitHub&lt;/a&gt;. To get started with development, please see the &lt;a href="https://github.com/jupyterlab/jupyterlab/blob/master/CONTRIBUTING.md"&gt;Contributing Guide&lt;/a&gt; and &lt;a href="https://github.com/jupyter/governance/blob/master/conduct/code_of_conduct.md"&gt;Code of Conduct&lt;/a&gt;. Many issues are ideal for new contributors and are tagged as “&lt;a href="https://github.com/jupyterlab/jupyterlab/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22"&gt;good first issue&lt;/a&gt;” or “&lt;a href="https://github.com/jupyterlab/jupyterlab/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22"&gt;help wanted&lt;/a&gt;”.&lt;/li&gt;
&lt;li&gt;Connect with the community on &lt;a href="https://github.com/jupyterlab/jupyterlab/issues"&gt;GitHub&lt;/a&gt; or on the &lt;a href="https://gitter.im/jupyterlab/jupyterlab"&gt;Gitter Channel&lt;/a&gt;. If you find a bug, have questions, or want to provide feedback, please join the conversation!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And don’t forget to participate in the &lt;a href="https://blog.jupyter.org/posts/2020/survey-jupyterlab-and-beyond/"&gt;user survey&lt;/a&gt;: &lt;a href="https://www.surveymonkey.com/r/LCB7GBF"&gt;https://www.surveymonkey.com/r/LCB7GBF&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Thanks!&lt;/p&gt;
&lt;div class="main-content-end" aria-hidden="true"&gt;&lt;/div&gt;
</content><category term="extensions"/><category term="JupyterLab"/><category term="releases"/></entry><entry><title>Reusable code snippets in JupyterLab</title><link href="https://blog.jupyter.org/posts/2020/reusable-code-snippets-in-jupyterlab/" rel="alternate"/><published>2020-11-18T18:08:00+00:00</published><updated>2020-11-19T15:38:00+00:00</updated><author><name>Patrick Titzler</name></author><id>tag:blog.jupyter.org,2020-11-18:/posts/2020/reusable-code-snippets-in-jupyterlab/</id><summary type="html">&lt;p&gt;In this blog post, we will introduce the Elyra code snippet extension, which enables us to reuse arbitrary snippets of code in your notebooks, source code, or markdown files in JupyterLab.&lt;/p&gt;</summary><content type="html">&lt;figure&gt;
&lt;img alt="Reusable code snippets in JupyterLab" src="https://blog.jupyter.org/posts/2020/reusable-code-snippets-in-jupyterlab/images/001-1_bF2CvMkAeI9QB_DD29M9Ow.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Reusable code snippets in JupyterLab&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In this blog post, we will introduce the &lt;a href="https://github.com/elyra-ai/elyra"&gt;Elyra&lt;/a&gt; &lt;a href="https://elyra.readthedocs.io/en/latest/user_guide/code-snippets.html"&gt;code snippet extension&lt;/a&gt;, which enables us to reuse arbitrary snippets of code in your notebooks, source code, or markdown files in JupyterLab.&lt;/p&gt;
&lt;p&gt;Let’s say you are working on a notebook and want to include a standard header or footer (like licensing text) or some code that you’ve previously created. This typically results in a hectic search. In which notebook did I use the code? Where’s the most current version of the disclaimer I need to add? While maintaining a “scratchpad” might help, native support for reusable code or documentation assets in JupyterLab holds a lot more appeal.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Code snippet ui" src="https://blog.jupyter.org/posts/2020/reusable-code-snippets-in-jupyterlab/images/002-1_cR1hInF2oXYu8hbosLx78w.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Code snippet ui&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="installing-the-code-snippet-extension"&gt;Installing the code snippet extension&lt;/h2&gt;
&lt;p&gt;The code snippet extension can be installed as part of Elyra or individually using &lt;code&gt;pip&lt;/code&gt; or &lt;code&gt;conda&lt;/code&gt;. In the context of this article, see below how to install the code snippet extension in your existing JupyterLab deployment.&lt;/p&gt;
&lt;h3 id="installing-the-extension-using-pip"&gt;Installing the extension using pip&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;elyra-code-snippet-extension&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;jupyter&lt;span class="w"&gt; &lt;/span&gt;lab&lt;span class="w"&gt; &lt;/span&gt;build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="installing-the-extension-using-anaconda"&gt;Installing the extension using Anaconda&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;conda&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;conda-forge&lt;span class="w"&gt; &lt;/span&gt;elyra-code-snippet-extension&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;jupyter&lt;span class="w"&gt; &lt;/span&gt;lab&lt;span class="w"&gt; &lt;/span&gt;build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="managing-and-using-code-snippets"&gt;Managing and using code snippets&lt;/h2&gt;
&lt;p&gt;Code snippets are accessed by opening the &lt;code&gt;code snippets&lt;/code&gt; tab on the JupyterLab sidebar:&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2020/reusable-code-snippets-in-jupyterlab/images/003-0_Fie0v3ppwrmiRTGX.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;The code snippets UI is divided into two parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a quick search panel, and&lt;/li&gt;
&lt;li&gt;the code snippet list, providing access to code snippet specific actions, such as copy, insert, edit, and delete.&lt;/li&gt;
&lt;/ul&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2020/reusable-code-snippets-in-jupyterlab/images/004-0_engxXB3Tz8HmkISW.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;h2 id="creating-a-snippet"&gt;Creating a snippet&lt;/h2&gt;
&lt;p&gt;To create a snippet click &lt;code&gt;+&lt;/code&gt; above the search bar and provide the required information. You can optionally tag snippets to make them more easily discoverable.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2020/reusable-code-snippets-in-jupyterlab/images/005-0_yIlAqD2eSLLkNo-0.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;h2 id="finding-a-snippet"&gt;Finding a snippet&lt;/h2&gt;
&lt;p&gt;To locate a code snippet, enter a search term and/or pick one of the listed tags.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2020/reusable-code-snippets-in-jupyterlab/images/006-0_oyIjiBDeFhi0y4It.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;h2 id="copying-a-snippet-to-the-clipboard"&gt;Copying a snippet to the clipboard&lt;/h2&gt;
&lt;p&gt;You copy the snippet content to the clipboard by clicking the snippet’s &lt;em&gt;copy&lt;/em&gt; icon.&lt;/p&gt;
&lt;h2 id="inserting-a-snippet"&gt;Inserting a snippet&lt;/h2&gt;
&lt;p&gt;Click the snippet’s insert icon to paste the snippet content in the desired destination, such as a cell in a notebook or an open editor window. Drag-and-Drop of the snippet has also been incorporated in the latest Elyra release.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2020/reusable-code-snippets-in-jupyterlab/images/007-0_QdPljYYKAYvqaP0Q.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;Do note that the editor does not keep track of where you’ve inserted a snippet. Also, note that snippets don’t act like macros and changes to a snippet are therefore not reflected in the locations where the snippet was embedded.&lt;/p&gt;
&lt;h2 id="editing-a-snippet"&gt;Editing a snippet&lt;/h2&gt;
&lt;p&gt;You edit a snippet by clicking the snippet’s pencil icon.&lt;/p&gt;
&lt;h2 id="deleting-a-code-snippet"&gt;Deleting a code snippet&lt;/h2&gt;
&lt;p&gt;You delete a code snippet by clicking the snippet’s trash icon.&lt;/p&gt;
&lt;h2 id="sharing-code-snippets"&gt;Sharing code snippets&lt;/h2&gt;
&lt;p&gt;The code snippet extension currently doesn’t provide any import or export capabilities. However, you can access the relevant metadata files that contain the definitions.&lt;/p&gt;
&lt;p&gt;The definitions are stored in the &lt;code&gt;$JUPYTER_DATA_DIR/metadata/code-snippets&lt;/code&gt; directory, where &lt;code&gt;$JUPYTER_DATA_DIR&lt;/code&gt; refers to the &lt;a href="https://jupyter.readthedocs.io/en/latest/use/jupyter-directories.html#id2"&gt;Jupyter data directory&lt;/a&gt;, which you can identify by running the following command in a terminal window:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;jupyter&lt;span class="w"&gt; &lt;/span&gt;--data-dir
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In my environment, the code snippets are stored in &lt;code&gt;/Users/ptitzler/Library/Jupyter/metadata/code-snippets/&lt;/code&gt;. To back up or share my snippets, I typically create an archive from the content of this directory.&lt;/p&gt;
&lt;h2 id="try-code-snippet-from-binder"&gt;Try Code Snippet from Binder&lt;/h2&gt;
&lt;p&gt;To experiment with the code snippet extension without installing it locally, just click on the binder link below and then look for the code-snippet icon on the JupyterLab side-bar :&lt;/p&gt;
&lt;figure&gt;
&lt;a href="https://mybinder.org/v2/gh/elyra-ai/elyra/v1.4.1?urlpath=lab/tree/binder-demo"&gt;&lt;img alt="Try the code snippet extension online on Binder" src="https://blog.jupyter.org/posts/2020/reusable-code-snippets-in-jupyterlab/images/008-0_BdqSU9blQk8YG6kC.webp" loading="lazy" data-body-image=""&gt;&lt;/a&gt;
&lt;figcaption&gt;Try the code snippet extension online on Binder&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="acknowledgments"&gt;Acknowledgments&lt;/h2&gt;
&lt;p&gt;We’d like to thank &lt;a href="https://www.linkedin.com/in/zach-sailer-8a1472151/"&gt;Zach Sailer&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/tgeorgeux/"&gt;Timothy George&lt;/a&gt; for all his UX contributions in conjunction with &lt;a href="https://www.linkedin.com/in/jay-jaewook-ahn/"&gt;JaeWook (Jay) Ahn&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/kiran-pinnipati/"&gt;Kiran Pinnipati&lt;/a&gt;, and &lt;a href="https://www.linkedin.com/in/ai-vyndang/"&gt;Ai-Vy Dang&lt;/a&gt; the CalPoly interns that helped to implement these UX enhancements.&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;Elyra is constantly making Jupyter Notebooks better aiming to help data scientists, machine learning engineers, and AI developers through the model development life cycle complexities, making JupyterLab even better for AI practitioners.&lt;/p&gt;
&lt;p&gt;Come and join our &lt;a href="https://github.com/elyra-ai/elyra#weekly-dev-community-meeting"&gt;weekly dev meetings&lt;/a&gt; to learn more about the most recent updates to Elyra and to ask your questions to the community, including the project core developers.&lt;/p&gt;
&lt;p&gt;We would also appreciate for you to get involved with the &lt;a href="https://github.com/elyra-ai/elyra/"&gt;Elyra project&lt;/a&gt;. Read our contributing guidelines, &lt;a href="https://github.com/elyra-ai/elyra/issues"&gt;create new issues&lt;/a&gt; if you have questions, suggestions for new features, or to report any bugs. We also welcome contributions via GitHub &lt;a href="https://github.com/elyra-ai/elyra/pulls"&gt;pull requests&lt;/a&gt;. And last but not least, don’t forget to star the project on Github.&lt;/p&gt;
&lt;div class="main-content-end" aria-hidden="true"&gt;&lt;/div&gt;
</content><category term="extensions"/><category term="JupyterLab"/></entry><entry><title>JupyterLab, the Cloud Robotics Command Station</title><link href="https://blog.jupyter.org/posts/2020/jupyterlab-ros/" rel="alternate"/><published>2020-08-11T13:19:00+00:00</published><updated>2020-08-11T13:20:00+00:00</updated><author><name>Carlos Herrero</name></author><id>tag:blog.jupyter.org,2020-08-11:/posts/2020/jupyterlab-ros/</id><summary type="html">&lt;p&gt;Building a cloud robotics development platform using JupyterLab and ROS&lt;/p&gt;
</summary><content type="html">&lt;figure&gt;
&lt;img alt="JupyterLab-ROS with Voilà to quickly make a standalone web app" src="https://blog.jupyter.org/posts/2020/jupyterlab-ros/images/001-1_zfaBPxiCzlnoNoKrVpcrIA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;JupyterLab-ROS with Voilà to quickly make a standalone web app&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The ROS open-source project (Robot Operating System) has become a &lt;em&gt;de-facto&lt;/em&gt; industry standard in the robotics community. Very much like with Project Jupyter, a large collection of software has been built upon well-specified communication protocols. ROS has become a rich ecosystem of standardized tools for building and distributing ROS-based software, exploratory robotics development, and much more.&lt;/p&gt;
&lt;p&gt;With &lt;em&gt;Industry 4.0&lt;/em&gt;, the number of robots and smart devices has been increasing exponentially. We are talking about fully automated factories with hundreds of robots and thousands of connected sensors, generating large amounts of raw data.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There is an opportunity in bridging the ROS and the open-source data science ecosystems and tools.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For this reason, we set ourselves to bring together these ecosystems,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;by producing a collection of JupyterLab extensions for ROS, turning JupyterLab into a &lt;em&gt;cloud robotics command station&lt;/em&gt;,&lt;/li&gt;
&lt;li&gt;by integrating ROS packages with conda / mamba package management system, to enable their installation alongside other open-source data-science packages.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="jupyterlab-the-cloud-robotics-command-station"&gt;JupyterLab, the cloud robotics command station&lt;/h2&gt;
&lt;p&gt;While the traditional developer tools from the ROS stack such as RViz are desktop-based, we think that a robotics development environment running in a web browser would provide a lot of flexibility. Also, the ideal robotics developer platform should be plugin-based, and easily extensible to account for the diversity of use cases to enable, from simple interactive scripting to visualization or large datasets. Finally, we should build this environment upon broadly adopted foundations, with open governance, and not let a single entity hold the keys to the ROS ecosystem.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Jupyter is a natural candidate to serve as the foundation for a web-based robotics development environment.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;JupyterLab is a web-based interactive development environment that has many of the much-needed features&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In JupyterLab, everything is a plugin, including the core components to the application. Anyone can produce a “remix” of core and third-party JupyterLab extensions to their needs.&lt;/li&gt;
&lt;li&gt;Very much in the spirit of ROS, one of the keys to Jupyter’s success is that the project was built upon well-documented and specified protocols and file formats that anyone could implement.&lt;/li&gt;
&lt;li&gt;Jupyter is a multi-stakeholder project, not backed by a single corporation, but by a community of developers at a variety of companies, universities, as well as individual contributors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Today, we are announcing the first beta of JupyterLab-ROS, a collection of JupyterLab plugins to integrate ROS with the JupyterLab platform. We combine the best of the data-science and robotics worlds to help developers build custom solutions for the industry. ROS can run in a high-performance server while JupyterLab is accessed in a web-browser (on Windows, OS X or Linux).&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="JupyterLab-ROS" src="https://blog.jupyter.org/posts/2020/jupyterlab-ros/images/002-1_98A8hXDk_cgVqdoA6EPXAg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;JupyterLab-ROS&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;JupyterLab-ROS automatically starts a rosmaster and rosbridge_server making all the painful setup of WebSockets connections completely transparent to users. A status bar widget allows you to start and stop the rosmaster server with a simple click and displays its state in real-time. Internally this widget runs a launch file which can be changed from the JupyterLab settings UI to launch additional nodes.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Master status widget" src="https://blog.jupyter.org/posts/2020/jupyterlab-ros/images/003-1_TA29a27cM-21mdTkdBQQxA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Master status widget&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Another important component of the ROS stack is RViz, a desktop-based 3-D visualization tool. It’s web-based counterpart in JupyterLab is Zethus, started by Rapyuta Robotics. This plugin supports most of the display types supported by RViz. It also provides an info panel that displays the raw messages in realtime, and a web-based version of rqt_graph for visualizing the ROS node graph.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Zethus" src="https://blog.jupyter.org/posts/2020/jupyterlab-ros/images/004-1_lzap6vG9lEBSQsg9rj7zog.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Zethus&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;A robotics station would be incomplete without a tool for debugging. For that purpose, ROS provides rosconsole, a package which allows developers to send messages to rosout and make them available on every node. The console gives access to these debugging messages in JupyterLab and provides additional features such as filtering by level and by node.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Log Console" src="https://blog.jupyter.org/posts/2020/jupyterlab-ros/images/005-1_OAsQ5PQoERlx2Tmex25aLg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Log Console&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="robotics-notebooks"&gt;Robotics Notebooks&lt;/h2&gt;
&lt;p&gt;A key component to the Jupyter stack is the Notebook, an open-source web application that allows you to create and share documents that contain live code, equations, visualizations and narrative text. It’s frequently used to do data cleaning and transformation, numerical simulation, statistical modelling, data visualization, machine learning etc.&lt;/p&gt;
&lt;p&gt;The notebook could be the perfect tool for robotics researchers and educators to make (and share) experiments in control algorithms, working in a dynamic and interactive development environment that allows quick prototyping and exploratory analysis while having access to the sophisticated mathematical libraries already well integrated with Jupyter. It can be tedious to have a different platform for interacting with robots and processing the data obtained from these, JupyterLab-ROS combines both toolsets and enables an integrated workflow.&lt;/p&gt;
&lt;p&gt;At the same time, many projects are becoming more and more interdisciplinary. Currently, it is common to see projects mixing topics like machine learning and robotics. JupyterLab-ROS opens a new world of possibilities for machine learning researchers that are interested in applying their models to robots: now they have the possibility of connecting a robot to a development environment that they are very familiar with.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/RoboStack/jupyter-ros"&gt;JupyROS&lt;/a&gt; is another powerful library that leverages &lt;a href="https://github.com/jupyter-widgets/ipywidgets/"&gt;ipywidgets&lt;/a&gt; and &lt;a href="https://github.com/bqplot/bqplot"&gt;bqplot&lt;/a&gt; to integrate ROS in Jupyter Notebooks, being able to show real time plots from ROS messages and create intuitive forms to control robots in minutes.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Interactive Robotics in the Jupyter Notebook, with live plotting" src="https://blog.jupyter.org/posts/2020/jupyterlab-ros/images/006-1_GN-BDT8ZT8GOcVZPKb44cg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Interactive Robotics in the Jupyter Notebook, with live plotting&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="ros-package-management"&gt;ROS Package management&lt;/h2&gt;
&lt;p&gt;Modern scientific computing applications typically depend on a large number of libraries. &lt;a href="https://conda-forge.org/"&gt;Conda-forge&lt;/a&gt; is a community initiative to package scientific libraries for the conda package manager, and has become a &lt;em&gt;de-facto&lt;/em&gt; standard source of packages for the scientific computing community, with tens of thousands of available packages, and thousands of contributors.&lt;/p&gt;
&lt;p&gt;During the last year, we have worked hard to package more and more of ROS as &lt;em&gt;conda packages&lt;/em&gt;. Conda-packages for ROS Melodic, including RViz are now available on all three operating systems! You can find the packages on the &lt;a href="https://anaconda.org/robostack"&gt;RoboStack channel&lt;/a&gt; (a more detailed blog post about this is going to follow soon).&lt;/p&gt;
&lt;p&gt;Conda can be used to create virtual environments for ROS projects, which makes it possible to install Melodic, Kinect and Dashing side-by-side on the same Linux system, and have exact control over package versions.&lt;/p&gt;
&lt;p&gt;We are also working on &lt;a href="https://github.com/TheSnakePit/mamba"&gt;Mamba&lt;/a&gt;, a conda-compatible package manager implemented in C++. Together with &lt;a href="https://github.com/TheSnakePit/boa"&gt;Boa&lt;/a&gt; this effort will help to continuously build and release ROS packages on conda.&lt;/p&gt;
&lt;h2 id="try-it-online"&gt;Try it online!&lt;/h2&gt;
&lt;p&gt;You can try the JupyterLab-ROS extension online without installing anything by just clicking on the following binder link:&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;a href="https://mybinder.org/v2/gh/RoboStack/jupyterlab-ros/master?urlpath=lab/tree/examples"&gt;&lt;img src="https://blog.jupyter.org/posts/2020/jupyterlab-ros/images/007-1_PZkzz4bGGPEyHlx-bbNRHg.webp" alt="Launch Binder" loading="lazy" data-body-image=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;JupyterLab-ROS is under active development. We are working on new features such as a rosbag widget to play and record bag files and improving existing ones like the roslaunch widget. If you are interested in contributing to JupyterLab-ROS you can find the code at &lt;a href="https://github.com/RoboStack/jupyterlab-ros"&gt;this repository&lt;/a&gt; in the RoboStack organization. Contributions are always welcome!&lt;/p&gt;
&lt;h2 id="installation"&gt;Installation&lt;/h2&gt;
&lt;p&gt;You can install JupyterLab-ROS using Mamba, the first step to create a new environment with JupyterLab and the ROS packages necessary, then you can install the extension.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mamba create -n test -c conda-forge -c robostack python=3.6 nodejs=12 jupyterlab ros-melodic-ros-core ros-melodic-rosauth ros-melodic-rospy ros-melodic-rosbridge-suite ros-melodic-rosbag ros-melodic-tf2-web-republisher

conda activate test

pip install jupyter-ros-server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you have any questions don’t hesitate to contact us on the &lt;a href="https://gitter.im/RoboStack/Lobby"&gt;RoboStack&lt;/a&gt; or &lt;a href="https://gitter.im/QuantStack/Lobby"&gt;QuantStack&lt;/a&gt; chats.&lt;/p&gt;
&lt;iframe src="https://www.youtube-nocookie.com/embed/vad9ZAUyw7k" title="JupyterLab ROS Demo" width="560" height="315" style="aspect-ratio: 560 / 315" loading="lazy" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;This work has many collaborators around the world, and we are especially grateful to &lt;a href="https://github.com/chaitanya-deep"&gt;Deep Chaitanya&lt;/a&gt; &amp;amp; &lt;a href="https://github.com/tocttou"&gt;Ashish Chaudhary&lt;/a&gt; from Rapyuta Robotics, &lt;a href="https://github.com/seanyen"&gt;Sean Yen&lt;/a&gt; from Microsoft for his amazing work on vinca that automagically generates the conda-recipes, &lt;a href="https://github.com/Tobias-Fischer"&gt;Tobias Fischer&lt;/a&gt; for many patches and pushing us to bring OS X support to the conda packages as well. Many thanks to &lt;a href="https://github.com/jtpio"&gt;Jeremy Tuloup&lt;/a&gt; for his help with the JupyterLab extension system.&lt;/p&gt;
&lt;div class="main-content-end" aria-hidden="true"&gt;&lt;/div&gt;
&lt;h2 id="about-the-authors"&gt;About the authors&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://twitter.com/wuoulf"&gt;Wolf Vollprecht&lt;/a&gt; works as a scientific and robotics software developer for QuantStack in Paris and Berlin.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2020/jupyterlab-ros/images/008-1_HnFdxHmGARUXhanv9pDCYg.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://twitter.com/CarlosHerreroB"&gt;Carlos Herrero&lt;/a&gt; is a Computer Engineer passionate about AI and his applications on robotics. Currently working at QuantStack helping to develop Open Source projects.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2020/jupyterlab-ros/images/009-1_uyE3rS6qzPDbNAJE5Mfx1A.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
</content><category term="extensions"/><category term="JupyterLab"/><category term="robotics"/></entry><entry><title>Elyra reaches 1.0.0</title><link href="https://blog.jupyter.org/posts/2020/elyra-reaches-1-0-0/" rel="alternate"/><published>2020-08-10T15:55:00+00:00</published><updated>2020-08-10T15:55:00+00:00</updated><author><name>Luciano Resende</name></author><id>tag:blog.jupyter.org,2020-08-10:/posts/2020/elyra-reaches-1-0-0/</id><summary type="html">&lt;p&gt;Building on a Jupyter Notebooks foundation, the de facto tool for data scientists, machine learning engineers and AI developers, Elyra is an open-source project that provides a set of AI-centric extensions to JupyterLab aiming to help users through the model development life cycle complexities…&lt;/p&gt;</summary><content type="html">&lt;p&gt;Building on a &lt;a href="https://jupyter.org/"&gt;Jupyter Notebooks&lt;/a&gt; foundation, the de facto tool for data scientists, machine learning engineers and AI developers, &lt;a href="https://github.com/elyra-ai/elyra/"&gt;Elyra&lt;/a&gt; is an open-source project that provides a set of AI-centric extensions to &lt;a href="https://github.com/jupyterlab/jupyterlab"&gt;JupyterLab&lt;/a&gt; aiming to help users through the model development life cycle complexities, making JupyterLab even better for AI practitioners.&lt;/p&gt;
&lt;p&gt;Elyra is proud to announce its &lt;a href="https://elyra.readthedocs.io/en/latest/getting_started/changelog.html#id1"&gt;1.0.0 Release&lt;/a&gt;. This release brings usability enhancements and bug fixes for existing features, such as enhanced inline user documentation and validation capabilities for the Pipeline Editor, improved performance for pipeline submission to Kubeflow Pipelines runtime. It also provides new capabilities such as a new reusable Code Snippets extension and the ability to configure runtimes directly on the JupyterLab user interface.&lt;/p&gt;
&lt;p&gt;On the platform side, Elyra 1.0.0 is now based on JupyterLab 2.x and can easily be integrated into a &lt;a href="https://github.com/jupyterhub/jupyterhub"&gt;JupyterHub&lt;/a&gt; deployment or other containerized environments such as Kubernetes and OpenShift or even be run in standalone mode using local docker deployment.&lt;/p&gt;
&lt;p&gt;With Elyra 1.0.0 we also provide some helpful use-case scenarios leveraging Elyra features to build AI pipelines in order to demonstrate in-depth capabilities exposed by the project. Use cases include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/CODAIT/covid-notebooks"&gt;Analyzing COVID-19 time-series data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/elyra-ai/examples/tree/master/pipelines/dax_noaa_weather_data"&gt;Analyzing NOAA weather time series data set and explore forecasting&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If this is the first time you are hearing about Elyra, check out our &lt;a href="https://developer.ibm.com/technologies/artificial-intelligence/blogs/open-source-elyra-ai-toolkit-simplifies-data-model-development/"&gt;announcement blog&lt;/a&gt; for more details about the project.&lt;/p&gt;
&lt;p&gt;The 1.0.0 release of Elyra includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Notebook Pipelines visual editor&lt;/li&gt;
&lt;li&gt;Ability to run notebooks as batch jobs&lt;/li&gt;
&lt;li&gt;Reusable Code Snippets (new)&lt;/li&gt;
&lt;li&gt;Hybrid runtime support (based on Jupyter Enterprise Gateway)&lt;/li&gt;
&lt;li&gt;Python script execution capabilities within the editor&lt;/li&gt;
&lt;li&gt;Python script navigation using auto-generated Table of Contents&lt;/li&gt;
&lt;li&gt;Notebook navigation using auto-generated Table of Contents&lt;/li&gt;
&lt;li&gt;Notebook versioning based on Git integration&lt;/li&gt;
&lt;li&gt;Reusable configuration and editor for runtimes (new)&lt;/li&gt;
&lt;li&gt;Support for JupyterLab 2.x (new)&lt;/li&gt;
&lt;li&gt;JupyterHub Support (new)&lt;/li&gt;
&lt;li&gt;Ability to try Elyra from Binder (new)&lt;/li&gt;
&lt;li&gt;Support for JupyterLab Dark Theme&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
&lt;img alt="Elyra 1.0 extensions to JupyterLab" src="https://blog.jupyter.org/posts/2020/elyra-reaches-1-0-0/images/001-1_np8Fau_8fuHGkT_hN5sg6w.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Elyra 1.0 extensions to JupyterLab&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="notebook-pipelines-visual-editor"&gt;Notebook Pipelines visual editor&lt;/h2&gt;
&lt;p&gt;Elyra’s Notebook Pipeline Editor simplifies the conversion of multiple notebooks into batch jobs or workflows. By leveraging cloud-based resources to run their experiments faster, the data scientists, machine learning engineers, and AI developers are then more productive, and therefore able to spend more of their time focusing on their technical skills.&lt;/p&gt;
&lt;p&gt;Based on the great feedback from the Elyra user base, this release brings lots of bug fixes usability enhancements such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enhanced inline user documentation&lt;/li&gt;
&lt;li&gt;Validation capabilities to Pipeline Editor that notifies users of missing or invalid configuration values&lt;/li&gt;
&lt;li&gt;Optimized dependency handling providing a much faster submission of pipelines&lt;/li&gt;
&lt;li&gt;Easier access to previously submitted experiments from the Pipeline Editor&lt;/li&gt;
&lt;li&gt;Support for “bring your own image” to be used as the environment to execute Notebooks on the external runtime&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Below you can see some of the Pipeline Editor UI enhancements introduced in Elyra 1.0:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Elyra Pipeline visual editor UI enhancements" src="https://blog.jupyter.org/posts/2020/elyra-reaches-1-0-0/images/002-1_P1HhivH4pkCeDqtxjEQTVw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Elyra Pipeline visual editor UI enhancements&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="reusable-code-snippets"&gt;Reusable Code Snippets&lt;/h2&gt;
&lt;p&gt;Code snippets give you the ability to save time and reuse task-oriented blocks of code. Elyra’s new code snippets extension enables easy discovery, creation, and insertion of reusable snippets of code into your Notebooks, Python Scripts, or even Markdown files used for documentation directly from the JupyterLab workspace. This makes the process of writing code more efficient and accessible.&lt;/p&gt;
&lt;p&gt;The list of available code snippets is found in the left side pane and includes a preview of each snippet, and an option to either copy a snippet or insert it directly inline.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Elyra code snippets pannel" src="https://blog.jupyter.org/posts/2020/elyra-reaches-1-0-0/images/003-1_yuU1PfPr5O04-V31EmRfbg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Elyra code snippets pannel&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Code snippets can also be conveniently created and edited from within JupyterLab.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Elyra code snippets editor" src="https://blog.jupyter.org/posts/2020/elyra-reaches-1-0-0/images/004-1_PzwPsYGRFeiDA_u0qFpAPw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Elyra code snippets editor&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="leveraging-table-of-contents-for-notebooks-and-python-scripts"&gt;Leveraging Table of Contents for Notebooks and Python Scripts&lt;/h2&gt;
&lt;p&gt;Navigating large files to find specific sections in Notebooks or function definitions in Python Scripts can be difficult tasks. The Table of Contents extension, which was enhanced to support navigating Python Scripts provides an easy outline of your contents, enabling easy navigation.&lt;/p&gt;
&lt;p&gt;Below is an example of an auto-generated table of contents for a Notebook based on its markdown sections:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Auto-generated Notebook Table of Contents" src="https://blog.jupyter.org/posts/2020/elyra-reaches-1-0-0/images/005-1_raOo4YPVH_m5f7FbmSUcwg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Auto-generated Notebook Table of Contents&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;To allow for streamlined python development, Elyra’s python editor is now accompanied by an auto-generated Table of Contents, which allows for efficient navigation within large python scripts.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Auto-generated Python Script outline using Table Of Contents" src="https://blog.jupyter.org/posts/2020/elyra-reaches-1-0-0/images/006-1_Ng0eU0ctSyNegygzSGcxTQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Auto-generated Python Script outline using Table Of Contents&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="reusable-configuration-and-editor-for-runtimes"&gt;Reusable configuration and editor for runtimes&lt;/h2&gt;
&lt;p&gt;Elyra introduced a ‘shared configuration service’ that simplifies workspace configuration management, enabling things like information around accessing external runtimes to be configured once and shared across multiple components.&lt;/p&gt;
&lt;p&gt;With Elyra 1.0, this service is now used by multiple components and has been enhanced with schema-based validation capabilities and a full set of REST APIs. With this release of Elyra, users can also easily browse, create, and edit these configurations from within the JupyterLab user interface.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Runtime configuration editor" src="https://blog.jupyter.org/posts/2020/elyra-reaches-1-0-0/images/007-1_7SfVLpa660T1GpxMvx87IQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Runtime configuration editor&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="jupyterhub-support"&gt;JupyterHub support&lt;/h2&gt;
&lt;p&gt;With Elyra 1.0.0 we have also created a docker image and provided necessary &lt;a href="https://elyra.readthedocs.io/en/latest/recipes/deploying-elyra-in-a-jupyterhub-endvironment.html"&gt;configuration steps&lt;/a&gt; to integrate Elyra with JupyterHub.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Elyra &amp;amp; JupyterHub deployment diagram" src="https://blog.jupyter.org/posts/2020/elyra-reaches-1-0-0/images/008-1_VpApg-iEYn2k2hW2HXUyQA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Elyra &amp;amp; JupyterHub deployment diagram&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="using-elyra-in-real-analytics-and-ai-scenarios"&gt;Using Elyra in real Analytics and AI scenarios&lt;/h2&gt;
&lt;p&gt;While building Elyra, we work very closely with data scientists, machine learning engineers, and AI developers, and we have been building a few scenarios to validate the user experience when developing models and other applications using Elyra.&lt;/p&gt;
&lt;h3 id="analyzing-covid-19-time-series-data"&gt;Analyzing COVID-19 time-series data&lt;/h3&gt;
&lt;p&gt;One of the examples creates a pipeline to analyze COVID-19 time series data sets from both the USA and Europe which is available as open-source in the &lt;a href="https://github.com/CODAIT/covid-notebooks"&gt;covid-notebook GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="COVID-19 notebook pipeline" src="https://blog.jupyter.org/posts/2020/elyra-reaches-1-0-0/images/009-1_b8SVOnIIbWqnJmShft8isw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;COVID-19 notebook pipeline&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="analyzing-noaa-weather-time-series-data-set-and-explore-forecasting"&gt;Analyzing NOAA weather time series data set and explore forecasting&lt;/h3&gt;
&lt;p&gt;Another example utilizes &lt;a href="https://developer.ibm.com/exchanges/data/"&gt;DAX — Data Asset Exchange NOAA dataset&lt;/a&gt; and produces a pipeline that consumes and applies ETL into the dataset, and then goes about analyzing and experimenting with different forecasting capabilities.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="NOAA weather time-series notebook pipeline" src="https://blog.jupyter.org/posts/2020/elyra-reaches-1-0-0/images/010-1_ep46vKAfyJoyxt5DZUvzEA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;NOAA weather time-series notebook pipeline&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="elyra-community-adoption"&gt;Elyra community adoption&lt;/h2&gt;
&lt;p&gt;The Elyra community is working very hard to promote adoption and create a healthy community around the project. In the past few months, we are starting to see some momentum, and below are some details :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Github Stars&lt;/strong&gt;: Elyra main repository is reaching close to 500 as of August 2020, please continue to show your support for the project by spreading the word about the project and giving us more stars.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Projects depending on Elyra:&lt;/strong&gt; Other then the two examples scenarios mentioned above, we are starting to see other communities adopting Elyra into their projects, CalPoly has been using the Elyra code snippets extension in their summer-intern projects and other communities are also experimenting with Elyra.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="GitHub projects depending on Elyra" src="https://blog.jupyter.org/posts/2020/elyra-reaches-1-0-0/images/011-1_aUQmatlEyLiOKHaEJyLTfQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;GitHub projects depending on Elyra&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Downloads:&lt;/strong&gt; We have also seen an increase of downloads of the Elyra npm packages in the past several weeks, and we are floating between 60k — 70k weekly downloads for the past month:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Elyra packages download stats" src="https://blog.jupyter.org/posts/2020/elyra-reaches-1-0-0/images/012-1_0bctBbTkft3J9fzAzVwwvg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Elyra packages download stats&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Elyra in the Enterprise&lt;/strong&gt;: Components of Elyra are integrated and available in IBM Cloud Pak for Data and Watson Studio offerings.&lt;/p&gt;
&lt;h2 id="try-elyra-from-binder"&gt;Try Elyra from Binder&lt;/h2&gt;
&lt;p&gt;To experiment with Elyra without installing it locally, just click on the binder link below:&lt;/p&gt;
&lt;figure&gt;
&lt;a href="https://mybinder.org/v2/gh/elyra-ai/elyra/v1.0.0?urlpath=lab/tree/binder-demo"&gt;&lt;img alt="Try Elyra online on Binder" src="https://blog.jupyter.org/posts/2020/elyra-reaches-1-0-0/images/013-1_-MRRK-LICe2zRigofJAA5Q.webp" loading="lazy" data-body-image=""&gt;&lt;/a&gt;
&lt;figcaption&gt;Try Elyra online on Binder&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="what-is-next-for-elyra"&gt;What is next for Elyra&lt;/h2&gt;
&lt;p&gt;Elyra is constantly making Jupyter Notebooks better aiming to help data scientists, machine learning engineers, and AI developers through the model development life cycle complexities, making JupyterLab even better for AI practitioners.&lt;/p&gt;
&lt;p&gt;Come and join our &lt;a href="https://github.com/elyra-ai/elyra#weekly-dev-community-meeting"&gt;weekly dev meetings&lt;/a&gt; to learn more about the most recent updates to Elyra and to ask your questions to the community, including the project core developers.&lt;/p&gt;
&lt;p&gt;We would also appreciate for you to get involved with the &lt;a href="https://github.com/elyra-ai/elyra/"&gt;Elyra project&lt;/a&gt;. Read our contributing guidelines, &lt;a href="https://github.com/elyra-ai/elyra/issues"&gt;create new issues&lt;/a&gt; if you have questions, suggestions for new features, or to report any bugs. We also welcome contributions via GitHub &lt;a href="https://github.com/elyra-ai/elyra/pulls"&gt;pull requests&lt;/a&gt;.&lt;/p&gt;
&lt;div class="main-content-end" aria-hidden="true"&gt;&lt;/div&gt;
</content><category term="extensions"/><category term="JupyterLab"/><category term="machine learning"/><category term="releases"/></entry></feed>