Hide code cell content
import graphviz
import IPython


@IPython.core.magic.register_line_cell_magic
def dot(line, cell):
    return graphviz.Source(cell)

For Starters#

The basic idea of a starter is:

  • pick a destination in the JupyterLab File Browser

  • click a button in the JupyterLab Launcher

  • see useful files

A slightly more accurate version is:

  • configure via traitlets

  • advertise to JupyterLab via the REST API

  • display in the JupyterLab Launcher

  • click a button in the JupyterLab Launcher

    • or immediately start with a Starter Tree URL

  • zero or more (but usually one) times:

    • gather more information from the user via react-jsonschema-form

    • perform further processing

    • copy files via the Contents API

    • see useful files in the JupyterLab File Browser

    • run JupyterLab Commands to do other things to JupyterLab

Which of these steps a particular starter performs depends primarily on its type.

Types of Starters#

Copy#

"type": "copy"

"src": "<an absolute or relative path>"

The simplest starter, copy, just… copies. It can copy a single file, or a directory of files (and subdirectories). The src attribute tells the starter where to get the files.

Hide code cell source
%%dot
digraph g { compound=true layout=dot rankdir=TB
    node[shape=none fontname="sans-serif"]
    graph[fontname="sans-serif" fontcolor="grey" color="none" fillcolor="#eeeeee" style=filled]
    label="a notional execution of a copy starter"
    subgraph cluster_files { label="Your Files"
        files
    }
    
    subgraph cluster_server { label="Notebook Server"
        get[label="/starters" fontname=monospace]
        post[label="/starters/{:name}/{:path}" fontname=monospace]
        contents
    }

    subgraph cluster_lab { label="JupyterLab"
        launcher
    }
    
    get -> launcher[label=①]
    launcher -> post[label=②]
    post -> contents[label=③]
    contents -> files[label=④]
    files -> contents[label=⑤]
    contents -> post[label=⑥]
    post -> launcher[label=⑦]
    launcher -> launcher[label=⑧]
}
../_images/2be37a69f5715329de0c083b689d533a2ffd2efa42ad3a110b688f2954a49aa6.svg

copy, like all the starters, makes use of the Contents API directly. Existing files will not be overwritten.

Content#

"type": "content"

"content": "<a subset of Jupyter Content>"

The Content starter is similar to a cookiecutter, supporting…

  • named contents, interpolated with jinja2-compatible templates for file contents and folder and file names

  • templated names that resolve as empty (and their children) will be skipped

…with the main differences being:

  • instead of an on-disk file tree, the enture content tree directly is defined inside the JSON definition.

  • when run entirely in the browser, nunjucks is used instead of jinja2

These can be provided (and run by) the server extension, or entirely within the browser when defined in JupyterLab (or JupyterLite).

Python#

"type": "python"

"callable": "<a dotted notation python function>"

A Python Starter is a function. This type has the fewest limitations, as it has full access to the StarterManager (and by extension, it’s parent, the NotebookApp). This powers both the Cookiecutter the Notebook starters, with the latter directly using the notebook server’s Kernel Manager to start short-lifespan kernels.

Hide code cell source
%%dot
digraph g { compound=true layout=dot rankdir=TB
    node[shape=none fontname="sans-serif"]
    graph[fontname="sans-serif" fontcolor="grey" color="none" fillcolor="#eeeeee" style=filled]
    label="a notional execution of a python starter"
    subgraph cluster_files { label="Your Files"
        files
    }
    
    subgraph cluster_server { label="Notebook Server"
        get[label="/starters" fontname=monospace]
        post[label="/starters/{:name}/{:path}" fontname=monospace]
        contents
        callable             
    }

    subgraph cluster_lab { label="JupyterLab"
        launcher
    }
    
    get -> launcher[label=①]
    launcher -> post[label=②]
    post -> callable[label=③]
    callable -> contents[label=④]
    contents -> files[label=⑤]
    files -> contents[label=⑥]
    contents -> callable[label=⑦]
    callable -> post[label=⑧]
    post -> launcher[label=⑨]
    launcher -> launcher[label=⑩]
}
../_images/664e46dd7ff34c9a8d4234f885cc9631aa3ec31be6d5ef848df750c10014c5ec.svg

Notebook#

"type": "notebook"

A notebook can be a starter. Each starter run gets its own, private kernel which can persist between interactions with the user. Communication with the server manager is handled through manipulating a copy of the notebook, specfically the notebook metadata. The advantages of this approach over the Python starter is:

  • works with any installed kernel

  • state is maintained between successive re-executions

  • jupyterlab-starters provides authoring support for editing and validating the starter

Hide code cell source
%%dot
digraph g { compound=true layout=dot rankdir=TB title="woooo"
    node[shape=none fontname="sans-serif"]
    graph[fontname="sans-serif" fontcolor="grey" color="none" fillcolor="#eeeeee" style=filled]
    label="a notional execution of a notebook starter"
    subgraph cluster_files { label="Your Files"
        files
    }
    
    subgraph cluster_server { label="Notebook Server"
        get[label="/starters" fontname=monospace]
        post[label="/starters/cookiecutter/{:path}" fontname=monospace]
        contents
        kernel
        tmpdir
    }

    subgraph cluster_lab { label="JupyterLab"
        launcher
        form1[label="initial form"]
        form2[label="dynamic form"]
    }

    get -> launcher[label=①]
    launcher -> form1[label=②]
    form1 -> post[label=③]
    post -> tmpdir[label=④]
    tmpdir -> post[label=⑤]
    tmpdir -> kernel[label=⑥]
    kernel -> tmpdir[label=⑦]
    post -> form2[label=⑧]
    form2 -> post[label=⑨]
    post -> tmpdir[label=⑩]
    tmpdir -> kernel[label=⑪]
    kernel -> tmpdir[label=⑫]
    tmpdir -> contents[label=⑬]
    contents -> files[label=⑭]
    files -> contents[label=⑮]
    contents -> post[label=⑯]
    post -> launcher[label=⑰]
    launcher -> launcher[label=⑲]
}
../_images/46a0f25e892eb4c248ae5d36522dde6861767721feb371be1dcfd98dad1374c7.svg

Built-ins#

Cookiecutter#

The cookiecutter starter will be available if cookiecutter is installed in the same Python environment as the notebook server.

Find more cookiecutter URLs on GitHub by topic or advanced search.

One of the original motivations for Jupyter Starters was a way to provide a convenient, consistent, web-based experience for the cookiecutter ecosystem. Briefly, a cookiecutter is:

  • a repository, zip archive, or directory that contains

    • cookiecutter.json

    • a (potentially nested) directory that uses Jinja2 to describe file names and contents

What they may lack in dynamism, the make up for in consistency and robustness.

Hide code cell source
%%dot
digraph g { compound=true layout=dot rankdir=TB title="woooo"
    node[shape=none fontname="sans-serif"]
    graph[fontname="sans-serif" fontcolor="grey" color="none" fillcolor="#eeeeee" style=filled]
    label="a notional execution of the cookiecutter starter"
    subgraph cluster_files { label="Your Files"
        files
    }
    
    subgraph cluster_server { label="Notebook Server"
        get[label="/starters" fontname=monospace]
        post[label="/starters/cookiecutter/{:path}" fontname=monospace]
        contents
        cookiecutter
    }

    subgraph cluster_lab { label="JupyterLab"
        launcher
        form1[label="template form"]
        form2[label="cookiecutter form"]
    }
    get -> launcher[label=①]
    launcher -> form1[label=②]
    form1 -> post[label=③]
    post -> cookiecutter[label=④]
    cookiecutter -> git[label=⑤]
    git -> cookiecutter[label=⑥]
    cookiecutter -> post[label=⑧]
    post -> form2[label=⑨]
    form2 -> post[label=⑩]
    post -> cookiecutter[label=⑪]
    cookiecutter -> contents[label=⑫]
    contents -> files[label=⑬]
    files -> contents[label=⑭]
    contents -> post[label=⑮]
    post -> launcher[label=⑯]
    launcher -> launcher[label=⑰]
}
../_images/8a045c8e856c4a6f2910901fc1df011ca3ac78de667094aee0f27b8123114f52.svg

Under the hood, the cookiecutter starter is implemented as a Python starter, and can be seen as tutorial in how to create a starter from a complex piece of existing functionality.

Extras#

Starter Tree URL#

By specifying a special URL when starting JupyterLab, you can immediately start a Starter, without requiring the launcher. The pattern is:

{:protocol}://{:host}:{:port}{:base-url}/lab{:whatever}?starter/{:starter-name}{:starter-path}

For example, to start the cookiecutter Starter:

http://localhost:8888/lab?starter=cookiecutter

On Binder, this path is determined by the urlpath GET parameter, which gets encoded again, for example:

https://mybinder.org/v2/gh/deathbeds/jupyterlab-starters/master?urlpath=lab%3Fstarter%2Fcookiecutter

Starter Body in URL#

The starter-body parameter is a JSON-encoded dictionary that conforms to the starter schema, and extends the above form:

&starter-body={:json-encoded-body}

For example, to pre-fill the form with the Jupyter Widget cookiecutter:

http://localhost:8888/lab?starter=cookiecutter&starter-body=%7B%22checkout%22%3A%22HEAD%22%2C%22directory%22%3A%22%22%2C%22template%22%3A%22https%3A%2F%2Fgithub.com%2Fjupyter-widgets%2Fwidget-cookiecutter%22%7D

Skip the Builder by URL#

The starter-form parameter is a JSON-encoded dictionary that conforms to the starter schema, and extends the above form:

&starter-form={:truthy}

Appending this to one of the above.

Note

Skipping the builder form can have undesirable effects for complex schema.