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.
%%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=⑧]
}
---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
Input In [2], in <module>
----> 1 get_ipython().run_cell_magic('dot', '', 'digraph g { compound=true layout=dot rankdir=TB\n node[shape=none fontname="sans-serif"]\n graph[fontname="sans-serif" fontcolor="grey" color="none" fillcolor="#eeeeee" style=filled]\n label="a notional execution of a copy starter"\n subgraph cluster_files { label="Your Files"\n files\n }\n \n subgraph cluster_server { label="Notebook Server"\n get[label="/starters" fontname=monospace]\n post[label="/starters/{:name}/{:path}" fontname=monospace]\n contents\n }\n\n subgraph cluster_lab { label="JupyterLab"\n launcher\n }\n \n get -> launcher[label=①]\n launcher -> post[label=②]\n post -> contents[label=③]\n contents -> files[label=④]\n files -> contents[label=⑤]\n contents -> post[label=⑥]\n post -> launcher[label=⑦]\n launcher -> launcher[label=⑧]\n}\n')
File ~/checkouts/readthedocs.org/user_builds/jupyterstarters/conda/stable/lib/python3.10/site-packages/IPython/core/displayhook.py:262, in DisplayHook.__call__(self, result)
260 self.start_displayhook()
261 self.write_output_prompt()
--> 262 format_dict, md_dict = self.compute_format_data(result)
263 self.update_user_ns(result)
264 self.fill_exec_result(result)
File ~/checkouts/readthedocs.org/user_builds/jupyterstarters/conda/stable/lib/python3.10/site-packages/IPython/core/displayhook.py:151, in DisplayHook.compute_format_data(self, result)
121 def compute_format_data(self, result):
122 """Compute format data of the object to be displayed.
123
124 The format data is a generalization of the :func:`repr` of an object.
(...)
149
150 """
--> 151 return self.shell.display_formatter.format(result)
File ~/checkouts/readthedocs.org/user_builds/jupyterstarters/conda/stable/lib/python3.10/site-packages/IPython/core/formatters.py:148, in DisplayFormatter.format(self, obj, include, exclude)
144 if self.ipython_display_formatter(obj):
145 # object handled itself, don't proceed
146 return {}, {}
--> 148 format_dict, md_dict = self.mimebundle_formatter(obj, include=include, exclude=exclude)
150 if format_dict or md_dict:
151 if include:
File ~/checkouts/readthedocs.org/user_builds/jupyterstarters/conda/stable/lib/python3.10/site-packages/decorator.py:232, in decorate.<locals>.fun(*args, **kw)
230 if not kwsyntax:
231 args, kw = fix(args, kw, sig)
--> 232 return caller(func, *(extras + args), **kw)
File ~/checkouts/readthedocs.org/user_builds/jupyterstarters/conda/stable/lib/python3.10/site-packages/IPython/core/formatters.py:222, in catch_format_error(method, self, *args, **kwargs)
220 """show traceback on failed format call"""
221 try:
--> 222 r = method(self, *args, **kwargs)
223 except NotImplementedError:
224 # don't warn on NotImplementedErrors
225 return self._check_return(None, args[0])
File ~/checkouts/readthedocs.org/user_builds/jupyterstarters/conda/stable/lib/python3.10/site-packages/IPython/core/formatters.py:973, in MimeBundleFormatter.__call__(self, obj, include, exclude)
970 method = get_real_method(obj, self.print_method)
972 if method is not None:
--> 973 return method(include=include, exclude=exclude)
974 return None
975 else:
File ~/checkouts/readthedocs.org/user_builds/jupyterstarters/conda/stable/lib/python3.10/site-packages/graphviz/jupyter_integration.py:98, in JupyterIntegration._repr_mimebundle_(self, include, exclude, **_)
96 include = set(include) if include is not None else {self._jupyter_mimetype}
97 include -= set(exclude or [])
---> 98 return {mimetype: getattr(self, method_name)()
99 for mimetype, method_name in MIME_TYPES.items()
100 if mimetype in include}
File ~/checkouts/readthedocs.org/user_builds/jupyterstarters/conda/stable/lib/python3.10/site-packages/graphviz/jupyter_integration.py:98, in <dictcomp>(.0)
96 include = set(include) if include is not None else {self._jupyter_mimetype}
97 include -= set(exclude or [])
---> 98 return {mimetype: getattr(self, method_name)()
99 for mimetype, method_name in MIME_TYPES.items()
100 if mimetype in include}
File ~/checkouts/readthedocs.org/user_builds/jupyterstarters/conda/stable/lib/python3.10/site-packages/graphviz/jupyter_integration.py:112, in JupyterIntegration._repr_image_svg_xml(self)
110 def _repr_image_svg_xml(self) -> str:
111 """Return the rendered graph as SVG string."""
--> 112 return self.pipe(format='svg', encoding=SVG_ENCODING)
File ~/checkouts/readthedocs.org/user_builds/jupyterstarters/conda/stable/lib/python3.10/site-packages/graphviz/piping.py:99, in Pipe.pipe(self, format, renderer, formatter, quiet, engine, encoding)
52 def pipe(self,
53 format: typing.Optional[str] = None,
54 renderer: typing.Optional[str] = None,
(...)
57 engine: typing.Optional[str] = None,
58 encoding: typing.Optional[str] = None) -> typing.Union[bytes, str]:
59 """Return the source piped through the Graphviz layout command.
60
61 Args:
(...)
97 '<?xml version='
98 """
---> 99 return self._pipe_legacy(format,
100 renderer=renderer,
101 formatter=formatter,
102 quiet=quiet,
103 engine=engine,
104 encoding=encoding)
File ~/checkouts/readthedocs.org/user_builds/jupyterstarters/conda/stable/lib/python3.10/site-packages/graphviz/_tools.py:172, in deprecate_positional_args.<locals>.decorator.<locals>.wrapper(*args, **kwargs)
163 wanted = ', '.join(f'{name}={value!r}'
164 for name, value in deprecated.items())
165 warnings.warn(f'The signature of {func.__name__} will be reduced'
166 f' to {supported_number} positional args'
167 f' {list(supported)}: pass {wanted}'
168 ' as keyword arg(s)',
169 stacklevel=stacklevel,
170 category=category)
--> 172 return func(*args, **kwargs)
File ~/checkouts/readthedocs.org/user_builds/jupyterstarters/conda/stable/lib/python3.10/site-packages/graphviz/piping.py:114, in Pipe._pipe_legacy(self, format, renderer, formatter, quiet, engine, encoding)
106 @_tools.deprecate_positional_args(supported_number=2)
107 def _pipe_legacy(self,
108 format: typing.Optional[str] = None,
(...)
112 engine: typing.Optional[str] = None,
113 encoding: typing.Optional[str] = None) -> typing.Union[bytes, str]:
--> 114 return self._pipe_future(format,
115 renderer=renderer,
116 formatter=formatter,
117 quiet=quiet,
118 engine=engine,
119 encoding=encoding)
File ~/checkouts/readthedocs.org/user_builds/jupyterstarters/conda/stable/lib/python3.10/site-packages/graphviz/piping.py:139, in Pipe._pipe_future(self, format, renderer, formatter, quiet, engine, encoding)
136 if encoding is not None:
137 if codecs.lookup(encoding) is codecs.lookup(self.encoding):
138 # common case: both stdin and stdout need the same encoding
--> 139 return self._pipe_lines_string(*args, encoding=encoding, **kwargs)
140 try:
141 raw = self._pipe_lines(*args, input_encoding=self.encoding, **kwargs)
File ~/checkouts/readthedocs.org/user_builds/jupyterstarters/conda/stable/lib/python3.10/site-packages/graphviz/backend/piping.py:196, in pipe_lines_string(engine, format, input_lines, encoding, renderer, formatter, quiet)
192 cmd = dot_command.command(engine, format,
193 renderer=renderer, formatter=formatter)
194 kwargs = {'input_lines': input_lines, 'encoding': encoding}
--> 196 proc = execute.run_check(cmd, capture_output=True, quiet=quiet, **kwargs)
197 return proc.stdout
File ~/checkouts/readthedocs.org/user_builds/jupyterstarters/conda/stable/lib/python3.10/site-packages/graphviz/backend/execute.py:83, in run_check(cmd, input_lines, encoding, capture_output, quiet, **kwargs)
81 assert kwargs.get('input') is None
82 assert iter(input_lines) is input_lines
---> 83 proc = _run_input_lines(cmd, input_lines, kwargs=kwargs)
84 else:
85 proc = subprocess.run(cmd, **kwargs)
File ~/checkouts/readthedocs.org/user_builds/jupyterstarters/conda/stable/lib/python3.10/site-packages/graphviz/backend/execute.py:109, in _run_input_lines(cmd, input_lines, kwargs)
106 for line in input_lines:
107 stdin_write(line)
--> 109 stdout, stderr = popen.communicate()
110 return subprocess.CompletedProcess(popen.args, popen.returncode,
111 stdout=stdout, stderr=stderr)
File ~/checkouts/readthedocs.org/user_builds/jupyterstarters/conda/stable/lib/python3.10/subprocess.py:1149, in Popen.communicate(self, input, timeout)
1146 endtime = None
1148 try:
-> 1149 stdout, stderr = self._communicate(input, endtime, timeout)
1150 except KeyboardInterrupt:
1151 # https://bugs.python.org/issue25942
1152 # See the detailed comment in .wait().
1153 if timeout is not None:
File ~/checkouts/readthedocs.org/user_builds/jupyterstarters/conda/stable/lib/python3.10/subprocess.py:2000, in Popen._communicate(self, input, endtime, orig_timeout)
1993 self._check_timeout(endtime, orig_timeout,
1994 stdout, stderr,
1995 skip_check_and_raise=True)
1996 raise RuntimeError( # Impossible :)
1997 '_check_timeout(..., skip_check_and_raise=True) '
1998 'failed to raise TimeoutExpired.')
-> 2000 ready = selector.select(timeout)
2001 self._check_timeout(endtime, orig_timeout, stdout, stderr)
2003 # XXX Rewrite these to use non-blocking I/O on the file
2004 # objects; they are no longer using C stdio!
File ~/checkouts/readthedocs.org/user_builds/jupyterstarters/conda/stable/lib/python3.10/selectors.py:416, in _PollLikeSelector.select(self, timeout)
414 ready = []
415 try:
--> 416 fd_event_list = self._selector.poll(timeout)
417 except InterruptedError:
418 return ready
KeyboardInterrupt:
copy
, like all the starters, makes use of the
Contents API
directly. Existing files will not be overwritten.
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.
%%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=⑩]
}
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
%%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=⑲]
}
Built-ins¶
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:
http://localhost:8888/lab?starter=cookiecutter/
On Binder, this path is determined by the urlpath
GET
parameter, for example:
https://mybinder.org/v2/gh/deathbeds/jupyterlab-starters/master?urlpath=lab%3Fstarter%2Fcookiecutter%2Fexamples%2F