Skip to content
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

DOC: Add interactive notebooks to pages in the "Usage Examples" section #741

Open
wants to merge 46 commits into
base: main
Choose a base branch
from

Conversation

agriyakhetarpal
Copy link
Collaborator

@agriyakhetarpal agriyakhetarpal commented May 9, 2024

Description

The Usage Examples section, following #728 and as requested in #737 (comment), is rendered interactive through the changes in this PR through the use of jupyterlite-sphinx and Markdown-based notebooks that are executed by MyST-NB. Please read below for a granular overview of all of these details:

Which issue does this PR solve/reference?

Addresses a part of #706

Key changes made

  1. All .rst based files under doc/source/regression converted to Markdown and reformatted as notebooks
  2. Jupytext frontmatter added to all of the notebooks so that they can be executed at the time of building the documentation
  3. Code cells that raise an error marked with cell-based tags
  4. MyST-NB is configured in conf.py
  5. NotebookLite directive used from jupyterlite-sphinx to run the notebooks under WASM in the same tab (and something like Add the option to open JupyterLite window in new tab jupyterlite/jupyterlite-sphinx#165 can be added for this directive as well)
  6. A custom, minimal Sphinx extension added to execute the Jupytext CLI before the sources and toctrees are read, and to selectively ignore the .ipynb-based notebooks that get generated during the process in order to keep the documentation build warning-free. Generating them at build time is for the NotebookLite directive to be able to access them and load them, since the NotebookLite directive currently does not load .md files or notebook files in other formats.
    i. I imagine this will be helpful for DOC: stats: Convert sampling tutorial to MyST-md scipy/scipy#20303 as well where it is required to load the notebooks in an interactive manner under a specific folder. The notebooks are not executed by Jupytext at the time of conversion, and therefore, based on past experiments, it takes ~10 seconds to convert 30 or so notebooks.

Additional context

This is just a pilot run of how notebook-based examples can be configured for Sphinx-based documentation websites, so there are a few corner cases that I have noticed so far:

  1. The styling of the NotebookLite directive can be improved because it takes way too much of the screen's space, perhaps through a user-provided option in conf.py just like how the TryExamples directive can be configured.
  2. References to the API for a package (in this case, public classes and methods for PyWavelets) are not linked to when running the notebook in its inline frame, because the notebook is not running under Sphinx and cannot access Sphinx's generated HTML pages. This can be worked around via better writing the notebooks by exploring other ways to reference pages and sections from the API (say, by adding Markdown-based headings or links, etc.).

A brief to-do list

Besides the points mentioned above, smaller tasks can be looked into:

  • Hide reST-based syntax (at least the irrelevant top parts of each page) using MyST
  • Improve the styling in other ways
  • Provide a button to download notebooks (both .md and .ipynb)

@agriyakhetarpal
Copy link
Collaborator Author

agriyakhetarpal commented May 9, 2024

Quite strange. The documentation is building without any problems locally...

Edit: I can reproduce locally if I delete a few of the previously generated files – this is coming from jupyterlite-sphinx. I think that Read the Docs is using a cached version of the documentation and is not purging the files properly, or it isn't allowing the subprocess to execute at the config-inited builder hook.

Edit, again: they were failing because the jupytext command was failing silently and was not converting any notebooks. Removing the hardcoded path fixed it.

@agriyakhetarpal agriyakhetarpal force-pushed the make-usage-examples-section-interactive branch 4 times, most recently from 8b4fcc8 to 4d1dcf0 Compare May 9, 2024 20:10
doc/source/conf.py Outdated Show resolved Hide resolved
doc/source/conf.py Outdated Show resolved Hide resolved
doc/source/conf.py Show resolved Hide resolved
doc/source/conf.py Outdated Show resolved Hide resolved
doc/source/regression/dwt-idwt.md Outdated Show resolved Hide resolved
Copy link

@steppi steppi left a comment

Choose a reason for hiding this comment

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

Thanks @agriyakhetarpal. Don't forget to set jupyterlite_silence = True again now that we're done debugging. After that I think this is good to merge.

cc @rgommers

doc/source/conf.py Outdated Show resolved Hide resolved
@rgommers
Copy link
Member

Don't forget to set jupyterlite_silence = True again now that we're done debugging. After that I think this is good to merge.

Looks like that last action was done, and everything is green. Are you happy with this PR as is @agriyakhetarpal?

@agriyakhetarpal
Copy link
Collaborator Author

Yes, I'm happy with this, @rgommers. I tested all notebooks, and everything seems to be working with jupyterlite-sphinx v0.16.2. I think this can be squash-merged, but I can rewrite history and ping you again if you wouldn't prefer that because of the number of files that have changed (and their contents).

Copy link
Member

@rgommers rgommers left a comment

Choose a reason for hiding this comment

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

Thanks for the hard work @agriyakhetarpal! The code and doc changes in this PR look carefully done and fine to me (modulo the rendered output discussion below), and things seem to work as advertised. Squash-merging is fine I think, no need to rewrite history.

I would like to follow up on the final result here. You already added a lot of comments in the PR description, and I largely agree with them:

The styling of the NotebookLite directive can be improved because it takes way too much of the screen's space, perhaps through a user-provided option in conf.py just like how the TryExamples directive can be configured.

Yes, this is a pretty significant regression I think. The output is too verbose, and also harder to understand because the only difference between input and output is a bit of green coloring on the left side of the input cells (rather than >>>). Also for accessibility that is not great. It looks fixable to me, since it's only a rendering choice.

The SciPy changes (scipy/scipy#20303) seem to have the same problem - I haven't been able to follow that work/review, so let me ping @melissawm here for thoughts.

The traceback rendering is also a lot worse. We go from this:

image

to this:

image

That's 14 extra lines of unwanted output that makes the example harder to understand.

References to the API for a package (in this case, public classes and methods for PyWavelets) are not linked to when running the notebook in its inline frame, because the notebook is not running under Sphinx and cannot access Sphinx's generated HTML pages. This can be worked around via better writing the notebooks by exploring other ways to reference pages and sections from the API (say, by adding Markdown-based headings or links, etc.).

Wouldn't it be better to drop most/all of the links? Something like {func}`dwt` isn't too clear in the downloaded or in-browser notebooks. It'd be okay to have fewer links from long-form docs to API docs I think.

Jupytext frontmatter added to all of the notebooks so that they can be executed at the time of building the documentation

Not a showstopper by itself, but it's a lot of boilerplate that I imagine either a Sphinx extension or a bit of codegen can remove.


Taking a step back, a bigger-picture question: what are we getting from this that scikit-learn isn't getting from sphinx-gallery? My impression is that we lost the individual interactive buttons from the jupyterlite-sphinx .. try-examples:: directive, and we end up with something that's harder to maintain and visually not as nice as what sphinx-gallery does? I'm probably missing something important here, but I don't quite see it right now.

```{eval-rst}
.. currentmodule:: pywt

.. dropdown:: 🧑‍🔬 This notebook can be executed online. Click this section to try it out! ✨
Copy link
Member

Choose a reason for hiding this comment

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

Unresolving this to keep the discussion visible - this is an important topic I think. The sphinx-gallery example renders a lot nicer visually; the sidebar links are much cleaner and it shouldn't require as much boilerplate in the .md files to get those links. It looks like it'd be good for pydata-sphinx-theme or a separate extension to have something similar.

Not a show-stopper for PyWavelets, because there's only a handful of .md files. But I doubt that what we have here will work for a much larger project.

@melissawm
Copy link

Styling-wise (please keep in mind I'm no frontend developer 😅 ) would it look better/make it more visually distinctive if we had labels, such as the following screenshot?

Captura de imagem_20240723_164429

This is just some CSS so would not be too much of a burden and we could apply it to SciPy as well.

As far as the gallery question, my take is that the main goal here is to have live notebooks that can be edited and run from the browser immediately without having to fire up a server locally.

@agriyakhetarpal
Copy link
Collaborator Author

Thanks for the detailed review, @rgommers!

The styling of the NotebookLite directive can be improved because it takes way too much of the screen's space, perhaps through a user-provided option in conf.py just like how the TryExamples directive can be configured.

Yes, this is a pretty significant regression I think. The output is too verbose, and also harder to understand because the only difference between input and output is a bit of green coloring on the left side of the input cells (rather than >>>). Also for accessibility that is not great. It looks fixable to me, since it's only a rendering choice.

I think @steppi would have more to comment about the NotebookLite directive's button's sizing. Now that I've revisited this after some time from when I made that comment: my understanding of the problem is that the clickable element, i.e., the yellow button with the "Open notebook" text resides in jupyterlite_sphinx_iframe_container, which has a minimum height set in conf.py (in this case, this is 600px, a size that seems to fit a viewable/scrollable section of the notebooks reasonably). If we reduce this, we'll need to figure out a way to expand the div/container based on whether it was clicked or loaded, similar to how TryExamples directives create a container upon interaction.

The SciPy changes (scipy/scipy#20303) seem to have the same problem - I haven't been able to follow that work/review, so let me ping @melissawm here for thoughts.

Yes, that work involved @melissawm expanding the JupyterLite directive to add a :new_tab: option and then using it to load the notebooks in a new tab – while I could have implemented the same thing here, the reason why I didn't do so was that the length of the notebooks in scipy/scipy#20303 was large, while notebooks here are considerably shorter and therefore not a lot of scrolling is needed (perhaps with the exception of "Wavelet Packets", which is lengthier than the rest).

The traceback rendering is also a lot worse
That's 14 extra lines of unwanted output that makes the example harder to understand.

I agree. I'm not sure how to improve this, but I'll try exploring the MyST-NB configuration to see if they have something to control the stack level for the traceback.

Wouldn't it be better to drop most/all of the links? Something like {func}dwt isn't too clear in the downloaded or in-browser notebooks. It'd be okay to have fewer links from long-form docs to API docs I think.

Yes, sure – I'll do that in a subsequent commit and drop them all. Keeping API terms in regular backticks without any links seems fine to me. I had thought of doing this as a last resort, but I do get that the "API reference" section is always one click (and then a few) away via the navbar, so users have easy access to classes and methods.

Jupytext frontmatter added to all of the notebooks so that they can be executed at the time of building the documentation

Not a showstopper by itself, but it's a lot of boilerplate that I imagine either a Sphinx extension or a bit of codegen can remove.

I would suggest keeping the notebooks in this manner. I've posted a feature request for jupyterlite-sphinx to accept Jupytext notebooks in jupyterlite/jupyterlite-sphinx#191 (which I would be glad to do the work for, myself) and my suggestion is that having some codegen probably isn't worth the complexity, considering that the Markdown files contain syntax such as "```{code-cell}```" which is only relevant for notebooks, and generating the frontmatter at runtime means there is some missing context for those who will try to access these files and note that they contain the necessities for Jupytext syntax, but don't contain the frontmatter (for developers, this would mean that code editors' extensions won't recognise them as MyST-flavoured notebooks). I did try this idea with a Sphinx extension, however—on a branch with commits that I did not get to push—I faced issues with connecting to an appropriate Sphinx event that could modify the files before Sphinx read them at least once. It could be handled with a new Makefile target that runs before the docs build, though.


My impression is that we lost the individual interactive buttons from the jupyterlite-sphinx .. try-examples:: directive, and we end up with something that's harder to maintain and visually not as nice as what sphinx-gallery does? I'm probably missing something important here, but I don't quite see it right now.

I can certainly improve this by undertaking the task of writing a separate Sphinx extension for this or exploring Jinja-based templating. We do have the building blocks in the PR I linked above (sphinx-gallery/sphinx-gallery#1312). I am open to suggestions if we have to modify the approach entirely – rendering this PR a playground or a canary of sorts as a precursor to larger projects where similar changes in this manner will be carried out helps us in the way that we won't need to go back to the sounding board to improve the approach.

@rgommers
Copy link
Member

Styling-wise (please keep in mind I'm no frontend developer 😅 ) would it look better/make it more visually distinctive if we had labels, such as the following screenshot?

I think so, yes. In/Out labels will be familiar to many users, and even for those who have never used a notebook it's easy to understand. I'd say it's (module the vertical space taken) about the same as >>>.

As far as the gallery question, my take is that the main goal here is to have live notebooks that can be edited and run from the browser immediately without having to fire up a server locally.

Isn't that the same as what the "launch lite" button in the right sidebar of sphinx-gallery examples like https://scikit-learn.org/stable/auto_examples/classification/plot_classifier_comparison.html does?

@melissawm
Copy link

Isn't that the same as what the "launch lite" button in the right sidebar of sphinx-gallery examples like scikit-learn.org/stable/auto_examples/classification/plot_classifier_comparison.html does?

Yes, but that would only work for gallery examples. I'm not sure you could use sphinx-gallery to have that in other documents. Alternatively you could migrate all of the narrative documentation you want to be interactive to sphinx-gallery, but I'm not sure how that would impact the documentation organization.

@agriyakhetarpal
Copy link
Collaborator Author

agriyakhetarpal commented Jul 24, 2024

Wouldn't it be better to drop most/all of the links? Something like {func}dwt isn't too clear in the downloaded or in-browser notebooks. It'd be okay to have fewer links from long-form docs to API docs I think.

Yes, sure – I'll do that in a subsequent commit and drop them all. Keeping API terms in regular backticks without any links seems fine to me. I had thought of doing this as a last resort, but I do get that the "API reference" section is always one click (and then a few) away via the navbar, so users have easy access to classes and methods.

I did this in 1dc3a0f and replaced all of the links with just text that gets highlighted as code text. Though, now that I've looked at the rendered docs locally, I do think that this is a step back from having the links. The links are non-existent/don't work just in the interactive notebooks that JupyterLite brings forth, so removing them from the main documentation pages so that the interactive notebook gets to be cleaner doesn't seem to be the best way forward. @rgommers, please let me know if this was what you had in mind or whether there should be a way for jupyterlite-sphinx to clean up the links in the rendered notebook or something?

@melissawm
Copy link

whether there should be a way for jupyterlite-sphinx to clean up the links in the rendered notebook

This would definitely be an improvement, short of having the actual links (this is on the roadmap for MyST-nb/jupytext as far as I understand). But I'm not sure this should be done now, maybe it's a future feature request for jupyterlite-sphinx?

@rgommers
Copy link
Member

Alternatively you could migrate all of the narrative documentation you want to be interactive to sphinx-gallery, but I'm not sure how that would impact the documentation organization.

True, that doesn't work for everything. Neither does a single notebook though, since that loses the .. try-examples:: directive that actually adds inline interactivity in .rst files. Or is there a way to still insert that?

so removing them from the main documentation pages so that the interactive notebook gets to be cleaner doesn't seem to be the best way forward.

I think I agree. Perhaps we should ignore it for now, and indeed consider whether that syntax could be auto-cleaned by jupyterlite-sphinx. It can be done later I think, it's not that critical.

We do have the building blocks in the PR I linked above (sphinx-gallery/sphinx-gallery#1312). I am open to suggestions if we have to modify the approach entirely – rendering this PR a playground or a canary of sorts as a precursor to larger projects where similar changes in this manner will be carried out helps us in the way that we won't need to go back to the sounding board to improve the approach.

It would be useful to spend some time exploring this. I don't have concrete suggestions because I don't know the various code bases of the packages involved well enough. I'm worried about doing double work here though, so some coherent picture of what is common with sphinx-gallery and what packages/components to use for which task would be useful.

@melissawm
Copy link

My understanding is that the try_examples directive would be mostly for API examples, and that notebooklite and similar directives would be more for narrative pages so both approaches would be available via jupyterlite-sphinx - maybe @agriyakhetarpal can clarify?

@steppi
Copy link

steppi commented Jul 25, 2024

Would it be better have a button like for the try_examples directive which swaps the rendered notebook in place with an executable one that occupies the same amount of screen real estate? I don't think there's any reason in principle why we couldn't do that; it just hasn't been done yet.

@rgommers
Copy link
Member

Would it be better have a button like for the try_examples directive which swaps the rendered notebook in place with an executable one that occupies the same amount of screen real estate?

I don't think so, that seems pretty much always worse than a new tab for larger narrative docs.

@rgommers
Copy link
Member

We just had a chat about this with @agriyakhetarpal, @melissawm, @steppi and @Carreau. Outcomes:

  • sphinx-gallery isn't really reusable here, worries around it being too inflexible and too slow to build. Instead, some of the CSS may be useful; if so then just copy the relevant code.
  • We have three types of interactive docs: docstrings (in good shape), .rst files where individual pieces of code get .. try-examples:: directives (in good shape too), and "take this larger page of narrative docs and render as notebook with JupyterLite in a new tab (needs work). For that last item:
    • Only Markdown supported (jupytext notebooks), not .rst
    • We want two buttons at the top of the page (easier to do than in the sidebar), one to open as notebook in a new tab, one to download the .ipynb
    • No manual boilerplate in each .md file, this should at most be a single .. notebooklite:: directive, or a single setting in conf.py to opt into generating these buttons for all narrative docs pages
    • Next step: try implementing these buttons in jupyterlite-sphinx. It's okay to require sphinx-design for buttons if needed.

@Carreau
Copy link

Carreau commented Nov 29, 2024

The traceback rendering is also a lot worse. We go from this:

There is %xmode minimal (--InteractiveShell.xmode=Minimal), but which may be too minimal.

In [5]: %xmode Minimal
Exception reporting mode: Minimal


In [6]: def f():
   ...:     1/0
   ...:

In [7]: f()
ZeroDivisionError: division by zero

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants