8000 Add ThebeLab support to notebook style galleries by sdhiscocks · Pull Request #713 · sphinx-gallery/sphinx-gallery · GitHub
[go: up one dir, main page]

Skip to content

Add ThebeLab support to notebook style galleries #713

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/_static/theme_override.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@ a.sphx-glr-backref-module-sphinx_gallery {

.anim-state label {
display: inline-block;
}

.thebelab-cell {
font-size: 12px;
}
9 changes: 9 additions & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,15 @@ def setup(app):
# each code block
'capture_repr': ('_repr_html_', '__repr__'),
'matplotlib_animations': True,
'thebelab': {
'requestKernel': True,
'binderOptions': {
'repo': "sphinx-gallery/sphinx-gallery.github.io",
},
'kernelOptions': {
'path': "./dev"
}
},
}

# Remove matplotlib agg warnings from generated doc when using plt.show
Expand Down
42 changes: 42 additions & 0 deletions doc/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ file:
- ``show_memory`` (:ref:`show_memory`)
- ``show_signature`` (:ref:`show_signature`)
- ``binder`` (:ref:`binder_links`)
- ``thebelab`` (:ref:`thebelab_support`)
- ``first_notebook_cell`` and ``last_notebook_cell`` (:ref:`own_notebook_cell`)
- ``notebook_images`` (:ref:`notebook_images`)
- ``pypandoc`` (:ref:`use_pypandoc`)
Expand Down Expand Up @@ -971,6 +972,47 @@ Binder links will point to these notebooks.
See the Sphinx-Gallery `Sphinx configuration file <https://github.com/sphinx-gallery/sphinx-gallery/blob/master/doc/conf.py>`_
for an example that uses the `public Binder server <http://mybinder.org>`_.

.. _thebelab_support:

ThebeLab Support
=================

Sphinx-Gallery can enable execution of code and displaying of outputs directly
in documentation via use of `ThebeLab <https://thebelab.readthedocs.io/>`__. By
default, this will use the `public Binder server`_.

In order to enable ThebeLab with Sphinx-Gallery, a ThebeLab configuration is
required in ``conf.py``. The configuration must be provide as a dictionary,
which is serialised into JSON, for direct processing by ThebeLab. For specific
configuration options, see `ThebeLab Configuration Reference <https://thebelab.readthedocs.io/en/latest/config_reference.html>`__.
An example is given below::

sphinx_gallery_conf = {
...
'thebelab': {
'requestKernel': True,
'binderOptions': {
'repo': "<github_org>/<github_repo>",
},
'kernelOptions': {
'path': "<path>"
}
},
...
}

For every page generated, the ``kernelOptions.path`` will be updated, having
the path to the gallery directory appended to it. This is useful if your
examples import local code or require access to files.

.. warning::

The ``selector``, ``outputSelector`` and ``predefinedOutput`` values should
not be set in the ThebeLab configuration, as these are set by
Sphinx-Gallery to be compatible with the generated output. These can be
overridden by setting them in the configuration if required, but may break
support on Sphinx-Gallery generated pages.

.. _without_execution:

Building without executing examples
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
'_static/gallery*.css',
'_static/no_image.png',
'_static/broken_example.png',
'_static/binder_badge_logo.svg'
'_static/binder_badge_logo.svg',
'_static/thebelab_badge.svg',
]},
scripts=['bin/copy_sphinxgallery.sh', 'bin/sphx_glr_python_to_jupyter.py'],
url="https://sphinx-gallery.github.io",
Expand Down
5 changes: 5 additions & 0 deletions sphinx_gallery/_static/gallery.css
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ div.sphx-glr-footer {
text-align: center;
}

div.sphx-glr-thebelab-badge {
margin: 1em auto;
vertical-align: middle;
}

div.sphx-glr-download {
margin: 1em auto;
vertical-align: middle;
Expand Down
1 change: 1 addition & 0 deletions sphinx_gallery/_static/thebelab_badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions sphinx_gallery/gen_gallery.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import os
import pathlib
from xml.sax.saxutils import quoteattr, escape
import warnings

from sphinx.errors import ConfigError, ExtensionError
from sphinx.util.console import red
Expand Down Expand Up @@ -90,6 +91,7 @@ def __call__(self, gallery_conf, script_vars):
'inspect_global_variables': True,
'css': _KNOWN_CSS,
'matplotlib_animations': False,
'thebelab': None,
}

logger = sphinx_compatibility.getLogger('sphinx-gallery')
Expand Down Expand Up @@ -336,6 +338,31 @@ def call_memory(func):
% (css, _KNOWN_CSS))
if gallery_conf['app'] is not None: # can be None in testing
gallery_conf['app'].add_css_file(css + '.css')
if gallery_conf['thebelab'] is not None:
thebelab = gallery_conf['thebelab']
if not isinstance(thebelab, (dict, bool)):
raise ConfigError(
"'thebelab' parameter must be of type bool or dict,"
"got: %s." % type(thebelab))
if thebelab is True:
thebelab = gallery_conf['thebelab'] = dict()

selector = ".sphx-glr-code>:not(.sphx-glr-output)"
if thebelab.setdefault('selector', selector) != selector:
warnings.warn(
"thebe 'selector' may be incompatible with sphinx-gallery")
output_selector = ".sphx-glr-output"
if thebelab.setdefault(
'outputSelector', output_selector) != output_selector:
warnings.warn(
"thebe config 'ouputSelector' may be incompatible with "
"sphinx-gallery")
if not thebelab.setdefault('predefinedOutput', True):
warnings.warn(
"thebe config 'predefinedOutput' may be incompatible with "
"sphinx-gallery")
if 'path' not in thebelab.setdefault('kernelOptions', {}):
thebelab['kernelOptions']['path'] = "."

return gallery_conf

Expand Down
55 changes: 46 additions & 9 deletions sphinx_gallery/gen_rst.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import sys
import traceback
import codeop
import json

from sphinx.errors import ExtensionError

Expand Down Expand Up @@ -170,6 +171,27 @@ def __exit__(self, type_, value, tb):
<br />
<br />"""

# Elements for ThebeLab
thebelab_badge = """\n
.. container:: sphx-glr-thebelab-badge

.. image:: {0}
:target: #
:width: 260px

.. raw:: html

<script type="text/x-thebe-config">
{1}
</script>
<script src="https://unpkg.com/thebelab@latest/lib/index.js"></script>
<script>
$('.sphx-glr-thebelab-badge a').click(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't remember - does sphinx-gallery assume jquery is installed?

function(){{thebelab.bootstrap(); return false;}}
);
</script>
"""


def codestr2rst(codestr, lang='python', lineno=None):
"""Return reStructuredText code block from code string."""
Expand Down Expand Up @@ -947,15 +969,20 @@ def rst_blocks(script_blocks, output_blocks, file_conf, gallery_conf):

code_rst = codestr2rst(bcontent, lang=gallery_conf['lang'],
lineno=lineno) + '\n'
example_rst += ".. container:: sphx-glr-code\n\n"
if is_example_notebook_like:
example_rst += code_rst
example_rst += code_output
example_rst += indent(code_rst, ' '*4)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is minor, but instead of doing ' '* 8, I like to define a tab variable and then use multiples of that. e.g.

tab = '    '
indent(code_rst, tab*2)

Reduces one cognitive step of dividing the number by the number of spaces per tab :-)

if code_output.strip():
example_rst += " .. container:: sphx-glr-output\n"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to wrapping the cell / inputs / outputs in their own containers so they can be more explicitly tagged

example_rst += indent(code_output, ' '*8)
else:
example_rst += code_output
if code_output.strip():
example_rst += "\n .. container:: sphx-glr-output\n"
example_rst += indent(code_output, ' '*8)
if 'sphx-glr-script-out' in code_output:
# Add some vertical space after output
example_rst += "\n\n|\n\n"
example_rst += code_rst
example_rst += indent(code_rst, ' '*4)
else:
block_separator = '\n\n' if not bcontent.endswith('\n') else '\n'
example_rst += bcontent + block_separator
Expand Down Expand Up @@ -997,16 +1024,26 @@ def save_rst_example(example_rst, example_file, time_elapsed,
example_rst += ("**Estimated memory usage:** {0: .0f} MB\n\n"
.format(memory_used))

# Generate a binder URL if specified
binder_badge_rst = ''
# Generate a binder or ThebeLab URL if specified/required
badge_rst = ''
if gallery_conf['thebelab'] and "sphx-glr-code" in example_rst:
badge_rel_path = os.path.relpath(
os.path.join(glr_path_static(), 'thebelab_badge.svg'),
gallery_conf['src_dir'])
conf = copy.deepcopy(gallery_conf['thebelab'])
conf['kernelOptions']['path'] += '/' + os.path.relpath(
os.path.dirname(example_file),
gallery_conf['src_dir'])
badge_rst += thebelab_badge.format(
"/" + badge_rel_path.replace(os.path.sep, '/'),
json.dumps(conf))
if len(binder_conf) > 0:
binder_badge_rst += gen_binder_rst(example_file, binder_conf,
gallery_conf)
badge_rst += gen_binder_rst(example_file, binder_conf, gallery_conf)

fname = os.path.basename(example_file)
example_rst += CODE_DOWNLOAD.format(fname,
replace_py_ipynb(fname),
binder_badge_rst,
badge_rst,
ref_fname)
example_rst += SPHX_GLR_SIG

Expand Down
27 changes: 25 additions & 2 deletions sphinx_gallery/tests/test_full.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,8 +371,10 @@ def test_logging_std_nested(sphinx_app):
sphinx_app.srcdir, 'auto_examples', 'plot_log.rst')
with codecs.open(log_rst, 'r', 'utf-8') as fid:
lines = fid.read()
assert '.. code-block:: none\n\n is in the same cell' in lines
assert '.. code-block:: none\n\n is not in the same cell' in lines
assert ' .. code-block:: none\n\n'\
' is in the same cell' in lines
assert ' .. code-block:: none\n\n'\
' is not in the same cell' in lines


def _assert_mtimes(list_orig, list_new, different=(), ignore=()):
Expand Down Expand Up @@ -844,3 +846,24 @@ def test_binder_logo_exists(sphinx_app):
assert 'binder_badge_logo' in img_fname # can have numbers appended
assert op.isfile(img_fname)
assert 'https://mybinder.org/v2/gh/sphinx-gallery/sphinx-gallery.github.io/master?urlpath=lab/tree/notebooks/auto_examples/plot_svg.ipynb' in html # noqa: E501


def test_thebelab(sphinx_app):
src_dir = sphinx_app.srcdir
fname = op.join(src_dir, 'auto_examples', 'plot_numpy_matplotlib.rst')
with codecs.open(fname, 'r', 'utf-8') as fid:
lines = fid.read()

assert 'sphx-glr-thebelab-badge' in lines
assert 'sphinx_gallery/_static/thebelab_badge.svg' in lines
assert '<script type="text/x-thebe-config">' in lines

thebe_config = (
'"binderOptions": {"repo": "example/example-repo"}',
'"selector": ".sphx-glr-code>:not(.sphx-glr-output)"',
'"outputSelector": ".sphx-glr-output"',
'"predefinedOutput": true',
'"kernelOptions": {"path": "./auto_examples"}',
)
for option in thebe_config:
assert option in lines
15 changes: 9 additions & 6 deletions sphinx_gallery/tests/test_gen_rst.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,12 @@ def test_rst_empty_code_block(gallery_conf, tmpdir):

.. GENERATED FROM PYTHON SOURCE LINES 4-5

.. code-block:: python
.. container:: sphx-glr-code

.. code-block:: python


# just a comment"""
# just a comment"""
assert example_rst.rstrip('\n') == want_rst


Expand Down Expand Up @@ -493,11 +495,12 @@ def test_pattern_matching(gallery_conf, log_collector, req_pil):
gallery_conf.update(image_scrapers=(), reset_modules=())
gallery_conf.update(filename_pattern=re.escape(os.sep) + 'plot_0')

code_output = ('\n Out:\n\n .. code-block:: none\n'
code_output = ('\n Out:\n\n'
' .. code-block:: none\n'
'\n'
' Óscar output\n'
' log:Óscar\n'
' $\\langle n_\\uparrow n_\\downarrow \\rangle$'
' 2851 Óscar output\n'
' log:Óscar\n'
' $\\langle n_\\uparrow n_\\downarrow \\rangle$'
)
warn_output = 'RuntimeWarning: WarningsAbound'
# create three files in tempdir (only one matches the pattern)
Expand Down
5 changes: 5 additions & 0 deletions 82F7 sphinx_gallery/tests/tinybuild/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ def __call__(self, gallery_conf, fname):
'junit': op.join('sphinx-gallery', 'junit-results.xml'),
'matplotlib_animations': True,
'pypandoc': True,
'thebelab': {
'binderOptions': {
'repo': "example/example-repo",
},
},
}
nitpicky = True
highlight_language = 'python3'
Expand Down
0