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 71 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
50bd61c
DEP: Add MyST-NB as a docs dependency
agriyakhetarpal May 7, 2024
2b68c2e
DOC: Remove all reST files for Usage Examples
agriyakhetarpal May 7, 2024
bf5389c
DOC: Add configuration values for MyST content
agriyakhetarpal May 7, 2024
d46e304
DOC: Use Jupytext for usage examples (DWT-IDWT)
agriyakhetarpal May 7, 2024
3432906
DOC: Use Jupytext for usage examples (extras)
agriyakhetarpal May 7, 2024
5e42538
DOC: Use Jupytext for usage examples (Multilevel Wavelet Transforms)
agriyakhetarpal May 7, 2024
ef57274
DOC: Use Jupytext for usage examples (Signal Extension Modes)
agriyakhetarpal May 7, 2024
112e846
DOC: Use Jupytext for usage examples (Wavelet object)
agriyakhetarpal May 7, 2024
0db1ac5
DOC: Use Jupytext for usage examples (Wavelet Packets)
agriyakhetarpal May 7, 2024
b3a1904
DOC: Use Jupytext for usage examples (Wavelet Packets 2D)
agriyakhetarpal May 7, 2024
0b9817c
MAINT: Ignore Jupyter Notebook execution cache folder
agriyakhetarpal May 7, 2024
888540e
DOC: Add `NotebookLite` directive to all notebooks
agriyakhetarpal May 7, 2024
4aef679
DEP: Add Jupytext CLI to dependencies
agriyakhetarpal May 7, 2024
2dad68f
DOC: Custom Sphinx extension to invoke Jupytext
agriyakhetarpal May 9, 2024
aff50c8
MAINT: Ignore auto-generated notebooks
agriyakhetarpal May 9, 2024
8033905
STY, DOC: Fix issues from `prettier` and linters
agriyakhetarpal May 9, 2024
f066013
DOC, DEP: Add and configure `sphinx-design`
agriyakhetarpal May 10, 2024
61ac9cc
DOC: Enable downloads, add dropdown (Signal Extension Modes)
agriyakhetarpal May 10, 2024
7b243fb
DOC: Enable downloads, add dropdown (DWT-IDWT)
agriyakhetarpal May 10, 2024
e75294c
DOC: Enable downloads, add dropdown (Wavelet Packets 2D)
agriyakhetarpal May 10, 2024
e9cd5de
DOC: Enable downloads, add dropdown (Wavelet Packets)
agriyakhetarpal May 10, 2024
14648a9
DOC: Enable downloads, add dropdown (Wavelet object)
agriyakhetarpal May 10, 2024
152c380
DOC: Enable downloads, add dropdown (Multilevel Wavelet Transforms)
agriyakhetarpal May 10, 2024
685b5dd
DOC: Enable downloads, add dropdown (Gotchas)
agriyakhetarpal May 10, 2024
b3c85f7
DOC: Silence JupyterLite
agriyakhetarpal May 31, 2024
4bd6487
DOC: Normalise initial Markdown cell, add tags
agriyakhetarpal Jun 1, 2024
f2c613e
DEP: Add `nbformat` as a dependency
agriyakhetarpal Jun 1, 2024
295020e
DOC: Clean up Markdown notebooks at build time
agriyakhetarpal Jun 1, 2024
2feb9a9
DOC: Disable frozen module debugging warnings
agriyakhetarpal Jun 1, 2024
6b4d0e8
API: Remove unneeded `__future__` imports
agriyakhetarpal Jun 4, 2024
f0439b9
DOC: Clean up unneeded type conversions
agriyakhetarpal Jun 4, 2024
2e321dc
DOC: Fix sync for notebook conversion + cleanup
agriyakhetarpal Jun 5, 2024
9bd9e9e
STY: Fix deprecated Ruff configuration
agriyakhetarpal Jun 7, 2024
962aaf0
DOC: Use the `jupytext` API instead of its CLI
agriyakhetarpal Jun 7, 2024
4e3ecd2
DOC: Revise `preprocess_notebooks()` for conversion
agriyakhetarpal Jul 18, 2024
7a9bdb5
DOC: change `pygment` ➡️ `pygments`
agriyakhetarpal Jul 18, 2024
6865650
DOC: Add `strip_tagged_cells = True` confval
agriyakhetarpal Jul 18, 2024
269dddd
DOC: Add `"jupyterlite_sphinx_strip"` to notebooks
agriyakhetarpal Jul 18, 2024
25c2f38
DOC, DEP: Bump `jupyterlite-sphinx` to `>=0.16.1`
agriyakhetarpal Jul 18, 2024
c28c8a6
DOC: Silence JupyterLite
agriyakhetarpal Jul 19, 2024
1b93651
DOC, DEP: Bump `jupyterlite-sphinx` to `>=0.16.2`
agriyakhetarpal Jul 19, 2024
b121320
STY: Fix style errors
agriyakhetarpal Jul 19, 2024
bcdc025
DOC: Remove redundant options from MyST-NB
agriyakhetarpal Jul 19, 2024
5149a65
STY: Fix style errors
agriyakhetarpal Jul 19, 2024
b3cdd4c
DOC: Replace references to the API with standard code text
agriyakhetarpal Jul 24, 2024
2964686
Try to fix deprecation warning at doctest import
agriyakhetarpal Dec 30, 2024
114894e
Provide JupyterLite and download links via include file
gabalafou Dec 20, 2024
3f44dba
restyle notebook cells
gabalafou Dec 28, 2024
60f8843
mock and hide outputs, update styles
gabalafou Dec 28, 2024
07f381f
exclude cells tagged jupyterlite_sphinx_strip from md to ipynb conver…
gabalafou Dec 28, 2024
abcc8e5
Use better CSS selectors
gabalafou Dec 30, 2024
69f082a
Add README file to regression folder
gabalafou Dec 30, 2024
7a79a0c
Update regression/index.rst
gabalafou Dec 30, 2024
e1d6b50
Fail docs build if cell throws but does not have raises-exception tag
gabalafou Dec 30, 2024
38e24d6
improve notebook doc page header
gabalafou Dec 30, 2024
1630eba
formatting
gabalafou Dec 31, 2024
4c663d1
follow Melissa's style
gabalafou Dec 31, 2024
76d5544
fix dark mode
gabalafou Dec 31, 2024
fb0d107
Bump jupyterlite-sphinx to 0.17.1 to use notebooklite
gabalafou Jan 2, 2025
45f8023
suggestions from code review
gabalafou Jan 2, 2025
49df46d
Make the output left aligned with the input
gabalafou Jan 2, 2025
1b0ddfd
Improve CSS
gabalafou Jan 2, 2025
3402f88
Match font weight to captioned code block (plus bottom border)
gabalafou Jan 2, 2025
e6970f3
oops, put the border in the right place
gabalafou Jan 2, 2025
a8f287c
Merge pull request #4 from gabalafou/notebook-header-directive
agriyakhetarpal Jan 15, 2025
be2b72a
Bump and pin to `jupyterlite-pyodide-kernel` 0.5.1
agriyakhetarpal Jan 15, 2025
466f241
Bump to `jupyterlite-sphinx` >=0.18.0
agriyakhetarpal Jan 15, 2025
7353a09
Fix Prettier/pre-commit error
agriyakhetarpal Jan 15, 2025
d2b6019
Fix deprecations tests failures
agriyakhetarpal Jan 15, 2025
78c3475
Fix Prettier lint again
agriyakhetarpal Jan 15, 2025
410a3e3
Oops, drop deprecation warnings tests
agriyakhetarpal Jan 15, 2025
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pip-log.txt
doc/_build/
doc/build/
.jupyterlite.doit.db
/doc/jupyter_execute/
doc/source/regression/*.ipynb

# Editors
.idea
Expand Down
82 changes: 82 additions & 0 deletions doc/source/_static/myst-nb.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/* MyST-NB

This stylesheet targets elements output by MyST-NB that represent notebook
cells.

In some cases these rules override MyST-NB. In some cases they override PyData
Sphinx Theme or Sphinx. And in some cases they do not override existing styling
but add new styling. */

/* Set up a few variables for this stylesheet */
.cell,
.pywt-handcoded-cell-output {
--pywt-cell-input-border-left-width: .2rem;

/* This matches the padding applied to <pre> elements by PyData Sphinx Theme */
--pywt-code-block-padding: 1rem;

/* override mystnb */
--mystnb-source-border-radius: .25rem; /* match PyData Sphinx Theme */
}

.cell .cell_input::before {
content: "In";
border-bottom: var(--mystnb-source-border-width) solid var(--pst-color-border);
font-weight: var(--pst-font-weight-caption);

/* Left-aligns the text in this box and the one that follows it */
padding-left: var(--pywt-code-block-padding);
}

/* Cannot use `.cell .cell_input` selector because the stylesheet from MyST-NB
uses `div.cell div.cell_input` and we want to override those rules */
div.cell div.cell_input {
background-color: inherit;
border-color: var(--pst-color-border);
border-left-width: var(--pywt-cell-input-border-left-width);
background-clip: padding-box;
overflow: hidden;
}

.cell .cell_output,
.pywt-handcoded-cell-output {
border: var(--mystnb-source-border-width) solid var(--pst-color-surface);
border-radius: var(--mystnb-source-border-radius);
background-clip: padding-box;
overflow: hidden;
}

.cell .cell_output::before,
.pywt-handcoded-cell-output::before {
content: "Out";
display: block;
font-weight: var(--pst-font-weight-caption);

/* Left-aligns the text in this box and the one that follows it */
padding-left: var(--pywt-code-block-padding);
}

.cell .cell_output .output {
background-color: inherit;
border: none;
margin-top: 0;
}

.cell .cell_output,
/* must prefix the following selector with `div.` to override Sphinx margin rule on div[class*=highlight-] */
div.pywt-handcoded-cell-output {
/* Left-align the text in the output with the text in the input */
margin-left: calc(var(--pywt-cell-input-border-left-width) - var(--mystnb-source-border-width));
}

.cell .cell_output .output,
.cell .cell_input pre,
.cell .cell_output pre,
.pywt-handcoded-cell-output .highlight,
.pywt-handcoded-cell-output pre {
border-radius: 0;
}

.pywt-handcoded-cell-output pre {
border: none; /* MyST-NB sets border to none for <pre> tags inside div.cell */
}
44 changes: 27 additions & 17 deletions doc/source/_static/pywavelets.css
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
/* Custom CSS rules for the interactive documentation button */

.try_examples_button {
color: white;
background-color: #0054a6;
border: none;
padding: 5px 10px;
border-radius: 10px;
margin-bottom: 5px;
box-shadow: 0 2px 5px rgba(108,108,108,0.2);
font-weight: bold;
font-size: small;
color: white;
background-color: #0054a6;
border: none;
padding: 5px 10px;
border-radius: 10px;
margin-bottom: 5px;
box-shadow: 0 2px 5px rgba(108,108,108,0.2);
font-weight: bold;
font-size: small;
}

.try_examples_button:hover {
background-color: #0066cc;
transform: scale(1.02);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
cursor: pointer;
background-color: #0066cc;
transform: scale(1.02);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
cursor: pointer;
}

.try_examples_button_container {
display: flex;
justify-content: flex-start;
gap: 10px;
margin-bottom: 20px;
display: flex;
justify-content: flex-start;
gap: 10px;
margin-bottom: 20px;
}

/*
Admonitions on this site are styled with some top margin. This makes sense when
the admonition appears within the flow of the article. But when it is the very
first child of an article, its top margin gets added to the article's top
padding, resulting in too much whitespace.
*/
.admonition.pywt-margin-top-0 {
margin-top: 0;
}
118 changes: 111 additions & 7 deletions doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
# serve to show the default.

import datetime
import importlib.metadata
import os
import re
from pathlib import Path

import jinja2.filters
import numpy as np

import pywt
Expand All @@ -25,6 +26,76 @@
except TypeError:
pass

from sphinx.application import Sphinx

HERE = Path(__file__).parent


def preprocess_notebooks(app: Sphinx, *args, **kwargs):
"""Preprocess Markdown notebooks to convert them to IPyNB format."""

import jupytext
import nbformat

print("Converting Markdown files to IPyNB...")
for path in (HERE / "regression").glob("*.md"):
if any(path.match(pattern) for pattern in exclude_patterns):
continue
nb = jupytext.read(str(path))

# In .md to .ipynb conversion, do not include any cells that have the
# jupyterlite_sphinx_strip tag
nb.cells = [
cell for cell in nb.cells if "jupyterlite_sphinx_strip" not in cell.metadata.get("tags", [])
]

ipynb_path = path.with_suffix(".ipynb")
with open(ipynb_path, "w") as f:
nbformat.write(nb, f)
print(f"Converted {path} to {ipynb_path}")


# Should match {{ parent_docname }} or {{parent_docname}}
parent_docname_substitution_re = re.compile(r"{{\s*parent_docname\s*}}")

def sub_parent_docname_in_header(
app: Sphinx, relative_path: Path, parent_docname: str, content: list[str]
):
"""Fill in the name of the document in the header.

When regression/header.md is read via the include directive, replace
{{ parent_docname }} with the name of the parent document that included
header.md.

Note: parent_docname does not include the file extension.

Here is a simplified example of how this works.

Contents of header.md:

{download}`Download {{ parent_docname }}.md <{{ parent_docname }}.md>`

Contents of foobar.md:

```{include} header.md
```

After this function and others are run...

Contents of foobar.md:

{download}`Download foobar.md <foobar.md>`
"""
if not relative_path.match("regression/header.md"):
return

for i, value in enumerate(content):
content[i] = re.sub(parent_docname_substitution_re, parent_docname, value)


def setup(app):
app.connect("config-inited", preprocess_notebooks)
app.connect("include-read", sub_parent_docname_in_header)

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
Expand All @@ -38,6 +109,7 @@
extensions = [
'jupyterlite_sphinx',
'matplotlib.sphinxext.plot_directive',
'myst_nb',
'numpydoc',
'sphinx.ext.autodoc',
'sphinx.ext.autosummary',
Expand All @@ -47,15 +119,19 @@
'sphinx.ext.mathjax',
'sphinx.ext.todo',
'sphinx_copybutton',
'sphinx_design',
'sphinx_togglebutton',

]

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix of source filenames.
source_suffix = '.rst'
source_suffix = {
'.rst': 'restructuredtext',
'.md': 'myst-nb',
'ipynb': None, # do not parse IPyNB files
}

# The encoding of source files.
source_encoding = 'utf-8'
Expand Down Expand Up @@ -141,8 +217,8 @@
"show_prev_next": True,
"footer_start": ["copyright", "sphinx-version"],
"footer_end": ["theme-version"],
"pygment_light_style": "a11y-high-contrast-light",
"pygment_dark_style": "a11y-high-contrast-dark",
"pygments_light_style": "a11y-high-contrast-light",
"pygments_dark_style": "a11y-high-contrast-dark",
"icon_links": [
{
"name": "Discussion group on Google Groups",
Expand Down Expand Up @@ -200,6 +276,7 @@
# _static directory.
html_css_files = [
"pywavelets.css",
"myst-nb.css"
]

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
Expand Down Expand Up @@ -278,7 +355,12 @@

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['substitutions.rst', ]
exclude_patterns = [
'substitutions.rst',
'regression/header.md',
'regression/README.md',
'regression/*.ipynb' # exclude IPyNB files from the build
]

# numpydoc_show_class_members = False
numpydoc_class_members_toctree = False
Expand Down Expand Up @@ -310,3 +392,25 @@
Shall you encounter any issues, please feel free to report them on the
[PyWavelets issue tracker](https://github.com/PyWavelets/pywt/issues)."""
)

jupyterlite_silence = True
strip_tagged_cells = True

# -- Options for MyST-NB and Markdown-based content --------------------------

os.environ["PYDEVD_DISABLE_FILE_VALIDATION"] = "1"

# https://myst-nb.readthedocs.io/en/latest/configuration.html
nb_execution_mode = 'auto'
nb_execution_timeout = 60
nb_execution_allow_errors = False
nb_execution_raise_on_error = True
nb_render_markdown_format = "myst"
nb_remove_code_source = False
nb_remove_code_outputs = False

myst_enable_extensions = [
'amsmath',
'colon_fence',
'dollarmath',
]
54 changes: 54 additions & 0 deletions doc/source/regression/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Regression folder

This folder contains various useful examples illustrating how to use and how not
to use PyWavelets.

The examples are written in the [MyST markdown notebook
format](https://myst-nb.readthedocs.io/en/v0.13.2/use/markdown.html). This
allows each .md file to function simultaneously as documentation that can be fed
into Sphinx and as a source file that can be converted to the Jupyter notebook
format (.ipynb), which can then be opened in notebook applications such as
JupyterLab. For this reason, each example page in this folder includes a header template
that adds a blurb to the top of each page about how the page can be
run or downloaded as a Jupyter notebook.

There a few shortcomings to this approach of generating the code cell outputs in
the documentation pages at build time rather than hand editing them into the
document source file. One is that we can no longer compare the generated outputs
with the expected outputs as we used to do with doctest. Another is that we
lose some control over how we want the outputs to appear, unless we use a workaround.

Here is the workaround we created. First we tell MyST-NB to remove the generated
cell output from the documentation page by adding the `remove-output` tag to the
`code-cell` directive in the markdown file. Then we hand code the output in a
`code-block` directive, not to be confused with `code-cell`! The `code-cell`
directive says "I am notebook code cell input, run me!" The `code-block`
directive says, "I am just a block of code for documentation purposes, don't run
me!" To the code block, we add the `.pywt-handcoded-cell-output` class so that
we can style it to look the same as other cell outputs on the same HTML page.
Finally, we tag the handcoded output with `jupyterlite_sphinx_strip` so that we
can exclude it when converting from .md to .ipynb. That way only generated
output appears in the .ipynb notebook.

To recap:

- We use the `remove-output` tag to remove the **generated** code cell output
during .md to .html conversion (this conversion is done by MyST-NB).
- We use the `jupyterlite_sphinx_strip` tag to remove the **handcoded** output
during .md to .ipynb conversion (this conversion is done by Jupytext).

Example markdown:

```{code-cell}
:tags: [raises-exception, remove-output]
1 / 0
```

+++ {"tags" ["jupyterlite_sphinx_strip"]}

```{code-block} python
:class: pywt-handcoded-cell-output
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
```
Loading
Loading