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

Examples page #7

Merged
merged 113 commits into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from 66 commits
Commits
Show all changes
113 commits
Select commit Hold shift + click to select a range
b1d05ba
Add openmm cookbook
Yoshanuikabundi Apr 20, 2023
6ee79d7
Examples progress
Yoshanuikabundi Apr 27, 2023
413c1a7
Remove sphinx_gallery
Yoshanuikabundi May 31, 2023
75f45dc
Add gitpython to lint environment
Yoshanuikabundi May 31, 2023
45e6bd3
Remove nbsphinx_prolog
Yoshanuikabundi May 31, 2023
d8f197e
Download zip files with notebooks and files, not just notebooks
Yoshanuikabundi May 31, 2023
cd59e3e
Pre-processing notebooks basically works
Yoshanuikabundi Jun 6, 2023
7479768
Execute notebooks in parallel and only update changed notebooks
Yoshanuikabundi Jun 6, 2023
08dfb53
Set INTERCHANGE_EXPERIMENTAL and simplify delay code
Yoshanuikabundi Jun 6, 2023
2bacdde
Notes to self
Yoshanuikabundi Jun 6, 2023
d773f31
Interim fixes for extension
Yoshanuikabundi Jun 6, 2023
7926fb4
Fix module stuff
Yoshanuikabundi Jun 7, 2023
96dbb8f
Clean up removed notebooks and download latest release examples
Yoshanuikabundi Jun 7, 2023
a214387
Get the sphinx extension building the processed stuff
Yoshanuikabundi Jun 8, 2023
2c92744
Proof of concept gallery
Yoshanuikabundi Jun 8, 2023
975fa72
Simplify code and fix warnings
Yoshanuikabundi Jun 8, 2023
ac0a263
Fix css in cookbook
Yoshanuikabundi Jun 8, 2023
79f2e8a
Finish transition to MyST-NB
Yoshanuikabundi Jun 8, 2023
97a2705
Temporary fix for NGLView widgets.
Yoshanuikabundi Jun 20, 2023
7215221
Process all notebooks in single folder
Yoshanuikabundi Jun 20, 2023
b7a46e1
Refactor towards caching notebooks on Git branch
Yoshanuikabundi Jun 20, 2023
7fbaac3
Avoid unnecessary API requests AKA Generators are cool & I am a nerd
Yoshanuikabundi Jun 20, 2023
fa6b6b9
Order Sphinx hooks chronologically
Yoshanuikabundi Jun 20, 2023
0c956e7
Application -> Sphinx
Yoshanuikabundi Jun 20, 2023
23912d3
Typos
Yoshanuikabundi Jun 20, 2023
7071026
Merge branch 'cookbook_gha' into examples
Yoshanuikabundi Jun 21, 2023
3a644c8
Merge branch 'main' into examples
Yoshanuikabundi Jun 21, 2023
f8d3a90
Fix colab link generation
Yoshanuikabundi Jun 21, 2023
1b50d0f
Download cache if pre-processed notebooks are missing
Yoshanuikabundi Jun 21, 2023
69f9d21
Print out git version
Yoshanuikabundi Jun 21, 2023
a851793
Troubleshooting improvements for broken examples
Yoshanuikabundi Jun 21, 2023
eea7e04
Remove unnecessary + redundant remove_notebooks function
Yoshanuikabundi Jun 21, 2023
9d15655
Ping github in sphinx extension to test RTD internet access
Yoshanuikabundi Jun 21, 2023
512e8bd
Try multiping
Yoshanuikabundi Jun 21, 2023
4ed8dea
Remove ping test
Yoshanuikabundi Jun 21, 2023
3492931
Try cloning in RTD hook
Yoshanuikabundi Jun 21, 2023
8975f9c
Simplify git command
Yoshanuikabundi Jun 21, 2023
86a5b81
mv -> rsync
Yoshanuikabundi Jun 21, 2023
c09b28a
Install rsync + git
Yoshanuikabundi Jun 21, 2023
870fb42
Remove ls command
Yoshanuikabundi Jun 21, 2023
1746529
Don't print git version
Yoshanuikabundi Jun 21, 2023
5fde2ab
Fix include_css_files()
Yoshanuikabundi Jun 21, 2023
d3a993c
Remove unused imports
Yoshanuikabundi Jun 21, 2023
625736d
Move notebook grid CSS out of extension
Yoshanuikabundi Jun 21, 2023
5c8be5a
Update lint environment
Yoshanuikabundi Jun 21, 2023
0b8f567
Merge branch 'main' into examples
Yoshanuikabundi Jun 21, 2023
26f49bf
Exclude examples from link checker
Yoshanuikabundi Jun 21, 2023
fe95db1
Remove annoying print statement
Yoshanuikabundi Jun 21, 2023
7c3604b
Debug link check ignore conf
Yoshanuikabundi Jun 21, 2023
23cb681
Move linkcheck exclusions to sphinx extension
Yoshanuikabundi Jun 21, 2023
d3bdbce
Fix linkcheck exclusions
Yoshanuikabundi Jun 21, 2023
ff4b45b
Tell MyST-NB about codelinter
Yoshanuikabundi Jun 21, 2023
bf28b5c
Merge branch 'main' into examples
Yoshanuikabundi Jun 26, 2023
fd77c1e
Parse tag out of repo list
Yoshanuikabundi Jun 26, 2023
25c9e3e
Copy thumbnails correctly
Yoshanuikabundi Jun 27, 2023
d3e1308
Get thumbnails into gallery
Yoshanuikabundi Jun 27, 2023
6adbb09
Break up _cookbook.py
Yoshanuikabundi Jun 27, 2023
ba7ac0a
Formatting
Yoshanuikabundi Jun 27, 2023
a26c0a0
Pin nglview>=3.0.6 and remove JS included therein
Yoshanuikabundi Jun 27, 2023
42ba5b3
Initial category support
Yoshanuikabundi Jun 27, 2023
618337f
Rejib cache paths
Yoshanuikabundi Jun 27, 2023
ebdadbe
Add fragmenter and reminder for QCSubmit
Yoshanuikabundi Jun 27, 2023
5a9423d
Disable fragmenter for now
Yoshanuikabundi Jun 27, 2023
5c3bf09
Move zipped notebooks to source directory
Yoshanuikabundi Jun 28, 2023
1a822b1
Support categories in cookbook
Yoshanuikabundi Jun 28, 2023
9f17df0
Tweak categories and switch to new interchange branch
Yoshanuikabundi Jun 28, 2023
9576cd6
Print env in rtd and update TK/Interchange
Yoshanuikabundi Jul 3, 2023
4d30f76
Try and enable custom widgets in colab
Yoshanuikabundi Jul 3, 2023
189bbd9
Support branch/pr caches and update main every night
Yoshanuikabundi Jul 3, 2023
d76f3aa
Fix inputs
Yoshanuikabundi Jul 3, 2023
4087b98
Ergonomics
Yoshanuikabundi Jul 3, 2023
0b2b619
Ensure deploy subdirectory exists
Yoshanuikabundi Jul 3, 2023
532e455
Fix ergonomics
Yoshanuikabundi Jul 3, 2023
1b4b9bb
Add --ignore-unmatch
Yoshanuikabundi Jul 3, 2023
6969dff
Use multiline string in .readthedocs.yaml
Yoshanuikabundi Jul 3, 2023
1de772a
Use old-style test in readthedocs.yaml
Yoshanuikabundi Jul 3, 2023
d86b2d6
String equality in .readthedocs.yaml
Yoshanuikabundi Jul 3, 2023
6336d79
Debug rtd with ls
Yoshanuikabundi Jul 3, 2023
0823436
Try ls --tree
Yoshanuikabundi Jul 3, 2023
0c423ea
Work around absence of --tree
Yoshanuikabundi Jul 3, 2023
c1b4b81
Fix cache download path
Yoshanuikabundi Jul 3, 2023
222a143
Debug rtd
Yoshanuikabundi Jul 3, 2023
553b56b
Try apt-get tree
Yoshanuikabundi Jul 3, 2023
30ff339
Fix source path?
Yoshanuikabundi Jul 3, 2023
f610d71
Move glob outside of quotes
Yoshanuikabundi Jul 3, 2023
b1cdd08
Simplify .readthedocs.yaml
Yoshanuikabundi Jul 3, 2023
620964c
Include branch in colab link
Yoshanuikabundi Jul 3, 2023
03d1d40
Update colab links in Sphinx extension
Yoshanuikabundi Jul 3, 2023
d565d10
Consolidate code between .readthedocs.yaml and python
Yoshanuikabundi Jul 3, 2023
90231a6
Fix argument handling in proc_examples.py
Yoshanuikabundi Jul 3, 2023
5b13759
Fix import
Yoshanuikabundi Jul 3, 2023
c17a1c7
Swap quotes
Yoshanuikabundi Jul 3, 2023
b155ab9
Try backtick shell expansion
Yoshanuikabundi Jul 3, 2023
56fad31
Use shell logic in .readthedocs.yaml
Yoshanuikabundi Jul 3, 2023
139b9a4
Clean up PR cache when closed
Yoshanuikabundi Jul 4, 2023
7d43834
Trigger RTD build when cache is updated (except in PRs)
Yoshanuikabundi Jul 4, 2023
0c28089
Merge branch 'main' into examples
Yoshanuikabundi Jul 4, 2023
f60ed11
Clarify description
Yoshanuikabundi Jul 4, 2023
7143205
Single quotes
Yoshanuikabundi Jul 4, 2023
fa10eee
Show source repository in gallery
Yoshanuikabundi Jul 4, 2023
b45fdb2
Style top-of-notebook links
Yoshanuikabundi Jul 4, 2023
90effe8
Add experimental warnings and improve styling
Yoshanuikabundi Jul 4, 2023
3ed8f85
Newline in css
Yoshanuikabundi Jul 4, 2023
33b77bb
globals -> globals_
Yoshanuikabundi Jul 4, 2023
8ed4661
Switch run_notebook.sh to use jupyterlab
Yoshanuikabundi Jul 4, 2023
0689914
Add installation instructions
Yoshanuikabundi Jul 4, 2023
a2b33a9
zip -> tgz
Yoshanuikabundi Jul 4, 2023
3c53bfe
Add CLI example for run_notebook.sh
Yoshanuikabundi Jul 4, 2023
52d05dc
Simplify
Yoshanuikabundi Jul 4, 2023
457ae4b
Fix old imports
Yoshanuikabundi Jul 4, 2023
0350a44
More robust run_notebook.sh
Yoshanuikabundi Jul 4, 2023
dfcc8de
Migrate to setup-micromamba
Yoshanuikabundi Jul 4, 2023
708dc2e
ZIP -> .TGZ in install instructions
Yoshanuikabundi Jul 4, 2023
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
build/

# Cookbook files
source/examples/
source/_cookbook/

# Python stuff
Expand Down
7 changes: 7 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,12 @@ build:
os: ubuntu-20.04
tools:
python: "mambaforge-4.10"
apt_packages:
- rsync
- git
jobs:
post_install:
- git clone -v --depth=1 --single-branch --branch=_cookbook_data -- https://github.com/openforcefield/openff-docs.git build/_cookbook_data
- rsync -a build/_cookbook_data/ ./
conda:
environment: devtools/conda-envs/rtd_env.yml
5 changes: 4 additions & 1 deletion devtools/conda-envs/examples_env.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ dependencies:
- packaging
# Examples
- openff-toolkit-examples
- openff-interchange==0.3.5 # Pin Interchange 0.3.5 because 0.3.6 has a broken example
- openff-interchange # 0.3.6 has a broken example, but we're loading examples from the central-examples branch where it's fixed
Copy link
Member

Choose a reason for hiding this comment

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

0.3.7 is released, which should work, and with any luck the tarballs will actually be online in an hour or two from me writing this comment

- openff-nagl==0.2.2
# - openff-fragmenter
# - openff-qcsubmit
- gromacs >=2021=nompi*
- lammps
- rich
- jax
- dglteam/label/cu118::dgl
- parmed<4
- nglview>=3.0.6
8 changes: 8 additions & 0 deletions devtools/conda-envs/lint_env.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ dependencies:
# readthedocs dependencies
- sphinx>=5,<7
- myst-parser>=1,<2
# - myst-nb
- sphinx-notfound-page
- ipython >=8.8
- sphinx-design
- gitpython
- nbconvert
- nbformat
# Code example deps
- openff-toolkit-base
- openff-interchange-base
Expand All @@ -18,3 +24,5 @@ dependencies:
- git+https://github.com/openforcefield/openff-sphinx-theme.git@main
# Lints
- sphinxawesome-codelinter
# Sphinx
- git+https://github.com/Yoshanuikabundi/MyST-NB.git@upgrade-to-1
9 changes: 9 additions & 0 deletions devtools/conda-envs/rtd_env.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,19 @@ dependencies:
# readthedocs dependencies
- sphinx>=5,<7
- myst-parser>=1,<2
# - myst-nb
- sphinx-notfound-page
- ipython >=8.8
- sphinx-design
# Examples
- gitpython
- nbconvert
- nbformat
# Theme
- pip:
# Theme
- git+https://github.com/openforcefield/openff-sphinx-theme.git@main
# Lints
- sphinxawesome-codelinter
# Sphinx
- git+https://github.com/Yoshanuikabundi/MyST-NB.git@upgrade-to-1
123 changes: 123 additions & 0 deletions source/_ext/cookbook/_cookbook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""Implementation of the cookbook Sphinx extension"""
from __future__ import annotations

from dataclasses import dataclass
import json
from os import stat
from pathlib import Path
import shutil

from sphinx.application import Sphinx
from sphinx.config import Config

from .github import download_dir
from .notebook import (
insert_cell,
get_metadata,
find_notebooks,
notebook_zip,
set_metadata,
)
from .globals import (
EXEC_IPYNB_ROOT,
REPO_EXAMPLES_DIR,
CACHE_BRANCH,
COLAB_IPYNB_ROOT,
OPENFF_DOCS_ROOT,
ZIPPED_IPYNB_ROOT,
GITHUB_REPOS,
)


def inject_tags_index(notebook: dict) -> dict:
"""Inject an `index` directive containing the notebook's metadata tags"""

tags = get_metadata(notebook, "tags", ["untagged"])

return insert_cell(
notebook,
cell_type="markdown",
source=[
f"```{{index}} {', '.join(tags)}",
f"```",
],
)


def inject_links(app: Sphinx, notebook: dict, docpath: Path) -> dict:
user, repo, *path = str(docpath.relative_to(EXEC_IPYNB_ROOT)).split("/")
path = "/".join(path)

tag = get_metadata(notebook, "src_repo_tag", "main")

github_uri = (
f"https://github.com/{user}/{repo}/blob/{tag}/{REPO_EXAMPLES_DIR}/{path}"
)

# TODO: Test colab
colab_path = COLAB_IPYNB_ROOT.relative_to(OPENFF_DOCS_ROOT) / user / repo / path
colab_uri = f"https://colab.research.google.com/github/openforcefield/openff-docs/blob/{CACHE_BRANCH}/{colab_path}"

zip_path = notebook_zip(docpath).relative_to(app.srcdir)

return insert_cell(
notebook,
cell_type="markdown",
source=[
f"[Download Notebook](path:/{zip_path})",
f"[View in GitHub]({github_uri})",
f"[Open in Google Colab]({colab_uri})",
],
)


def process_notebook(app: Sphinx, docname: str, source: list[str]):
docpath = Path(app.env.doc2path(docname))
if docpath.suffix != ".ipynb":
return

notebook = json.loads(source[0])

notebook = inject_links(app, notebook, docpath)
notebook = inject_tags_index(notebook)

# Tell Sphinx we don't expect this notebook to show up in a toctree
set_metadata(notebook, "orphan", True)

source[0] = json.dumps(notebook)


def download_cached_notebooks(app: Sphinx, config: Config):
"""
Download notebooks from the cache iff they do not exist.

Note that the cache is not checked for changes; if you want to refresh the
cache, you must manually delete it.
"""
for directory in [
COLAB_IPYNB_ROOT,
EXEC_IPYNB_ROOT,
ZIPPED_IPYNB_ROOT,
]:
for repo in GITHUB_REPOS:
repo, _, tag = repo.partition("#")
repo_directory = directory / repo
if not repo_directory.exists():
download_dir(
"openforcefield/openff-docs",
str(repo_directory.relative_to(OPENFF_DOCS_ROOT)),
repo_directory,
refspec=CACHE_BRANCH,
)

# Exclude notebooks from linkcheck
config["linkcheck_exclude_documents"].extend(
str(doc.relative_to(app.srcdir)) for doc in Path(EXEC_IPYNB_ROOT).glob("**/*")
)


def find_notebook_docnames(app, env, docnames):
"""Find the downloaded notebooks and make sure Sphinx sees them"""
for path in find_notebooks(EXEC_IPYNB_ROOT):
docname = env.project.path2doc(str(path))
docnames.append(docname)
178 changes: 178 additions & 0 deletions source/_ext/cookbook/_gallery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
from pathlib import Path

from sphinx.environment import BuildEnvironment
from sphinx.util.docutils import SphinxDirective
import sphinx.addnodes
import docutils.nodes
from sphinx.writers.html5 import HTML5Translator
from docutils.parsers.rst.directives import choice
from sphinx.application import Sphinx

from ._cookbook import find_notebooks
from .globals import EXEC_IPYNB_ROOT
from .utils import flatten


class CookbookDirective(SphinxDirective):
"""
Directive to draw thumbnails of the cookbook.

The "categories" option specifies the categories of notebooks that should be
included in this cookbook. It is a comma-separated string listing category
names. Omitting this option will include all categories. The
names "uncategorized" and "other" are special; "uncategorized" is applied
to any notebook without a category, whereas "other" will include all
categories not rendered in a cookbook somewhere else on the same page as
the current directive (possibly including "uncategorized").
"""

optional_arguments = 1
option_spec = {
"categories": lambda x: [
s.strip().lower().replace("-", "_") for s in str(x).split(",")
],
}
has_content = False

def run(self):
node = CookbookNode(categories=self.options.get("categories", []))

for path in find_notebooks(EXEC_IPYNB_ROOT):
entry = CookbookEntryNode.from_path(self.env, path)
node.append(entry)

return [node]


class CookbookNode(docutils.nodes.Element):
"""Docutils node representing the cookbook directive"""

def __init__(
self,
categories: list[str] | None = None,
*args,
**kwargs,
):
self.categories: list[str] = categories if categories else []
self.children: list[CookbookEntryNode]
return super().__init__(*args, **kwargs)

@staticmethod
def visit(translator: HTML5Translator, node: "CookbookNode"):
"""Render the CookbookNode in HTML"""
translator.body.append("<div class='notebook-grid'>")

@staticmethod
def depart(translator: HTML5Translator, node: "CookbookNode"):
"""Render the CookbookNode in HTML"""
translator.body.append("</div>")


class CookbookEntryNode(docutils.nodes.Element):
def __init__(
self,
docname: str,
thumbnail_uri: str = "https://openforcefield.org/about/branding/img/openforcefield_v2_full-color.png",
*args,
**kwargs,
):
"""The absolute path to the notebook."""
self.docname: str = docname
"""The docname of the notebook."""
self.uri: str | None = None
"""URI of the built notebook."""
self.title: str | None = None
"""Title of the notebook."""

super().__init__(*args, **kwargs)

self.append(
docutils.nodes.image(
"",
uri=thumbnail_uri,
alt="",
candidates={"?": ""},
classes=["output", "image_png"],
)
)

@classmethod
def from_path(cls, env: BuildEnvironment, path: Path) -> "CookbookEntryNode":
docname = env.path2doc(str(path))

if docname is None:
raise ValueError(
f"path {path} is not a document in this Sphinx environment.",
)

if path.with_name("thumbnail.png").is_file():
thumbnail_uri = str(path.with_name("thumbnail.png").relative_to(env.srcdir))
return cls(docname=docname, thumbnail_uri=thumbnail_uri)

return cls(docname=docname)

@staticmethod
def visit(translator: HTML5Translator, node: CookbookNode):
"""Render the CookbookEntryNode in HTML"""
translator.body.extend(
[
f"<a class='notebook-grid-elem' href={node.uri}>",
]
)

@staticmethod
def depart(translator: HTML5Translator, node: CookbookNode):
"""Render the CookbookEntryNode in HTML"""
translator.body.extend(
[
"<div class='caption'>",
node.title,
"</div>",
"</a>",
]
)


def proc_cookbook_toctree(
app: Sphinx,
doctree: sphinx.addnodes.document,
docname: str,
):
"""Update the cookbook with URIs and titles"""
metadata = app.env.metadata

cookbook_nodes = [*doctree.findall(CookbookNode)]

# Get the categories that are in a cookbook directive on this page
categories_on_page = {*flatten(node.categories for node in cookbook_nodes)}

for cookbook_node in cookbook_nodes:
cookbook_categories = cookbook_node.categories

if cookbook_categories:
# "uncategorised" is a special category for notebooks whose metadata
# doesn't specify a category
cookbook_entries_with_categories = (
(entry, metadata[entry.docname].get("category", "uncategorized"))
for entry in cookbook_node.children
)
# "other" is a special category for cookbook directives that should
# include all notebooks not rendered in any other category on the
# current page.
# TODO: Make this all notebooks not rendered in the entire project?
cookbook_node.children = [
entry
for entry, entry_category in cookbook_entries_with_categories
if entry_category in cookbook_categories
or (
"other" in cookbook_categories
and entry_category not in categories_on_page
)
]

for entry in cookbook_node.children:
entry.title = app.env.titles[entry.docname].astext()
if entry.title == "<no title>":
entry.title = Path(entry.docname).stem.replace("_", " ")

entry.uri = app.builder.get_relative_uri(docname, entry.docname)
1 change: 1 addition & 0 deletions source/_ext/cookbook/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from tempfile import TemporaryDirectory
from importlib import import_module

from git.cmd import Git
from git.repo import Repo
import requests
from packaging.version import Version
Expand Down
Loading