Skip to content

Commit ffc7683

Browse files
authored
Merge pull request #1 from danielballan/copy-danielballan-interactive-tutorial-demo
Copy danielballan interactive tutorial demo
2 parents ecf32fa + cad071b commit ffc7683

16 files changed

+5116
-0
lines changed

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# GitHub syntax highlighting
2+
pixi.lock linguist-language=YAML linguist-generated=true

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
/build/*
2+
.ipynb_checkpoints
3+
/.pixi/*
4+
5+
# The following are boilerplate. It is best practice to handle this generically
6+
# in ~/.gitignore, but including it here is helpful for beginners.
17
# Byte-compiled / optimized / DLL files
28
__pycache__/
39
*.py[cod]

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Executable Tutorials
2+
3+
A repository demonstrating one way to manage and distribute interactive tutorials.
4+
See the [Guide to Contributing](https://scentific-python.org/executable-tutorials/contributing.html)
5+
for a good overview of what this is like to use.
6+
7+
## Demo Links
8+
9+
- **[Tutorials (a.k.a. recipes)](https://github.com/scientific-python/executable-tutorials/tree/notebooks/docs/recipes)** are written in MyST Markdown.
10+
- They are published in **[Jupyter notebook](https://github.com/scientific-python/executable-tutorials/tree/notebooks/docs/recipes)** format, on the `notebooks` branch of this repo, which can be used in Colab, Binder, etc.
11+
- Executed examples are **[published](https://scientific-python.github.io/exeuctable-tutorials/)** on a GitHub Pages site.
12+
- **[Jupyter Lite (beta)](https://scientific-python.github.io/executable-tutorials/jupyterlite/lab/index.html)** (works only on the basic executable example so far, missing dependencies for others)
13+
- **[Binder](https://mybinder.org/v2/gh/scientific-python/executable-tutorials/notebooks)**
14+
15+
16+
## Goals
17+
18+
- Make content easy to explore and try in a variety of modes:
19+
- interactive and non-interactive
20+
- local and cloud-based
21+
- Jupyter and not-Jupyter
22+
- Document an accessible development workflow, so that non-experts can contribute.
23+
- Keep the infrastructure as simple as possible.
24+
25+
## To Do
26+
27+
- Test notebook execution _of changed recipes only_ in CI on PR.
28+
- Set up devcontainer.
29+
- Add example with additional dependencies.
30+
31+
## Prior Art
32+
33+
Examples that this is drawing from:
34+
35+
- https://github.com/Caltech-IPAC/irsa-tutorials
36+
- https://github.com/MotherDuck-Open-Source/sql-tutorial
Loading

docs/conf.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
extensions = ["myst_nb", "sphinx_copybutton"]
2+
3+
html_theme = 'sphinx_book_theme'
4+
html_title = 'Executable Tutorials'
5+
html_theme_options = {
6+
"repository_url": "https://github.com/scientific-python/executable-tutorials",
7+
"path_to_docs": "docs",
8+
"repository_branch": "main",
9+
"use_repository_button": True,
10+
"use_issues_button": True,
11+
"use_edit_page_button": True,
12+
}
13+
nb_execution_mode = "auto"

docs/contributing.md

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# Guide to Contributing
2+
3+
First, clone this repository.
4+
5+
With HTTPS:
6+
7+
```sh
8+
git clone https://github.com/scientific-python/executable-tutorials
9+
```
10+
11+
With SSH:
12+
```sh
13+
git clone git@github.com:scientific-python/executable-tutorials
14+
```
15+
16+
## Overview
17+
18+
Each "recipe" is a directory under `docs/recipes/`. It may contain one or more
19+
Markdown (`.md`) files with a mixture of narrative text and code. Each recipe
20+
directory may also contain supporting data files, scripts, illustrations,
21+
solutions to exercises, etc.
22+
23+
```none
24+
$ tree docs/
25+
docs/
26+
├── conf.py
27+
├── contributing.md
28+
├── index.md
29+
├── recipes
30+
│   ├── executable
31+
│   │   ├── basics.md
32+
│   ├── matplotlib
33+
│   │   ├── interactive_mpl.md
34+
│   │   └── static_mpl.md
35+
│   └── static
36+
│   └── static.md
37+
...
38+
```
39+
40+
Some of these Markdown files include a header that describes how to convert
41+
them into Jupyter notebooks and execute the code in them. This is described in
42+
more detail below.
43+
44+
## Setup
45+
46+
We use the [pixi](https://pixi.sh/latest/#installation) package manager to
47+
create an environment with dependencies from conda-forge and PyPI. We recommend
48+
installing pixi. Then just:
49+
50+
```sh
51+
pixi install
52+
```
53+
54+
But if you have a strong preference for a different package manager, peek
55+
inside `pixi.toml` to find the list of dependencies and install them however
56+
you wish.
57+
58+
## Build
59+
60+
```sh
61+
pixi run build
62+
```
63+
64+
```{note}
65+
Later, if the build results get into a broken state, use `pixi run clean`
66+
to start fresh.
67+
```
68+
69+
This creates:
70+
- Executed notebooks, generated from eligible Markdown files, under `build/juptyer_execute/`
71+
- HTML under `build/html/`
72+
73+
Open `build/html/index.html` in a web browser.
74+
75+
## Develop
76+
77+
```
78+
pixi run jupyter lab
79+
```
80+
81+
In the file browser, locate one of the examples under `docs/recipes/`. Double
82+
click to open.
83+
84+
Files like `docs/recipes/static/static.md` are plain Markdown files. Files like
85+
`docs/recipes/executable/basics.md` have a YAML header which enables the
86+
documentation build system to convert them to Jupyter notebooks and execute
87+
them:
88+
89+
```yaml
90+
---
91+
jupytext:
92+
text_representation:
93+
extension: .md
94+
format_name: myst
95+
format_version: 0.13
96+
jupytext_version: 1.16.4
97+
kernelspec:
98+
display_name: Python 3 (ipykernel)
99+
language: python
100+
name: python3
101+
---
102+
```
103+
104+
One option is to simply edit these files in any text editor.
105+
106+
Another is to use Jupyter Lab, which can open these files _as notebooks_
107+
enabling you the interactively edit and execute them, saving the changes to
108+
Markdown.
109+
110+
![Context Menu: Open With... Jupytext Notebook](./_static/images/open-with-jupytext-notebook.png)
111+
112+
Note that the cell _outputs_ are not saved to disk. This is a feature, not a
113+
bug: the outputs are build products, and they do not belong in version control.
114+
During the build process, the Markdown document is converted to a Jupyter
115+
notebook format and executed.
116+
117+
## Test
118+
119+
The script `test.sh` runs files with executable code from top to bottom and
120+
prints any unexpected tracebacks.
121+
122+
```
123+
pixi run ./test.sh docs/recipes/executable/basics.md
124+
```
125+
126+
`````{note}
127+
128+
Sometimes examples are _expected_ to raise an exception. These can be marked up
129+
with a "tag" like so. With that tag, the build and test procedures will pass
130+
over the exception.
131+
132+
````markdown
133+
```{code-cell} ipython3
134+
:tags: [raises-exception]
135+
136+
1 / 0
137+
```
138+
````
139+
`````
140+
141+
To test _all_ examples, run:
142+
143+
```
144+
pixi run ./test.sh --all
145+
```
146+
147+
(Above, the script automatically discovers all Markdown files which have that
148+
YAML header marking them as executable.)
149+
150+
## Deploy
151+
152+
Once changes are merged to the `main` branch, the GitHub Actions [Publish workflow][] will:
153+
- Publish HTML to this site.
154+
- Uploaded the executed ipynb notebooks as a GitHub Artifact (useful for debugging)
155+
as an Artifact associated with the workflow run.
156+
- Push to [the `notebooks` branch of this repository][notebooks-branch] a version
157+
where all Markdown Jupytext files have been converted to `.ipynb` format, using
158+
`pixi run ./convert-all ipynb`. This is suitable for use in environments
159+
where users need to be handed Jupyter Notebook files directly, such as Google
160+
Colab.
161+
162+
[notebooks-branch]: https://github.com/scientific-python/executable-tutorials/tree/notebooks/docs/recipes
163+
[Publish workflow]: https://github.com/scientific-python/executable-tutorials/actions/workflows/cd.yml

docs/index.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Recipes
2+
3+
```{toctree}
4+
:maxdepth: 1
5+
:glob:
6+
recipes/**/*
7+
contributing
8+
```

docs/recipes/executable/basics.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
jupytext:
3+
text_representation:
4+
extension: .md
5+
format_name: myst
6+
format_version: 0.13
7+
jupytext_version: 1.16.4
8+
kernelspec:
9+
display_name: Python 3 (ipykernel)
10+
language: python
11+
name: python3
12+
---
13+
14+
# Executable Code
15+
16+
## Basics
17+
18+
```{code-cell} ipython3
19+
1 + 3
20+
```
21+
22+
```{code-cell} ipython3
23+
a = 8**2
24+
```
25+
26+
```{code-cell} ipython3
27+
a
28+
```
29+
30+
This cell has an expected error:
31+
32+
```{code-cell} ipython3
33+
:tags: [raises-exception]
34+
35+
1 / 0
36+
```
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
jupytext:
3+
text_representation:
4+
extension: .md
5+
format_name: myst
6+
format_version: 0.13
7+
jupytext_version: 1.16.4
8+
kernelspec:
9+
display_name: Python 3 (ipykernel)
10+
language: python
11+
name: python3
12+
---
13+
14+
# Interactive Matplotlib Figures
15+
16+
Specify the interactive Jupyter matplotlib backend, backed by ipympl. This must be run first, before any figures are created.
17+
18+
```{code-cell} ipython3
19+
%matplotlib widget
20+
```
21+
22+
Creating a new figure display an interactive canvas in Jupyter Lab.
23+
24+
If we do nothing else, this will display a snapshot of the currently-blank canvas in the rendered HTML documentation. To avoid that, we edit the Markdown file so that the cell below has the tag `hide-output`. This places the figure in an expandable box, hidden by fault. Alternatively, we could use `remove-output` to fully remove it.
25+
26+
````markdown
27+
```{code-cell} ipython3
28+
:tags: [hide-output]
29+
import matplotlib.pyplot as plt
30+
31+
32+
plt.plot([1,2,3])
33+
```
34+
````
35+
36+
```{code-cell} ipython3
37+
:tags: [hide-output]
38+
39+
import matplotlib.pyplot as plt
40+
41+
plt.figure()
42+
```
43+
44+
Plotting data to an existing figure updates the original interactive canvas in Jupyter Lab. Users can scroll up to pan and zoom.
45+
46+
To show an updated snapshot in the rendered HTML documentation, we should call `plt.gcf()` to display the current Figure.
47+
48+
```{caution}
49+
If you re-render the canvas---such as by displaying `plt.gcf().canvas`---that will cause the cached snapshot of the figures above to update to show the latest version of the figure, ruining the sequential narrative in the rendered HTML documentation.
50+
51+
This is due to a detail of the matplotlib--Jupyter interaction. Just know to use `plt.gcf()` to safely show snapshots.
52+
```
53+
54+
```{code-cell} ipython3
55+
plt.plot([1, 2, 3, 4])
56+
```
57+
58+
```{code-cell} ipython3
59+
plt.gcf()
60+
```
61+
62+
```{code-cell} ipython3
63+
plt.plot([1, 4, 9, 16])
64+
plt.gcf()
65+
```

docs/recipes/matplotlib/static_mpl.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
jupytext:
3+
text_representation:
4+
extension: .md
5+
format_name: myst
6+
format_version: 0.13
7+
jupytext_version: 1.16.4
8+
kernelspec:
9+
display_name: Python 3 (ipykernel)
10+
language: python
11+
name: python3
12+
---
13+
14+
# Static Matplotlib Figures
15+
16+
Specify the static Jupyter matplotlib integration. This must be run first, before any figures are created.
17+
18+
This disables any interactivity, displaying only a PNG image of the figure in the Jupyter Lab view.
19+
20+
```{code-cell} ipython3
21+
%matplotlib inline
22+
```
23+
24+
```{code-cell} ipython3
25+
import matplotlib.pyplot as plt
26+
27+
plt.plot([1,2,3,4])
28+
```
29+
30+
31+
```{code-cell} ipython3
32+
import matplotlib.pyplot as plt
33+
34+
plt.plot([1,4,9,16])
35+
```

0 commit comments

Comments
 (0)