From d40259681fd4711f25d364571a497def207ec473 Mon Sep 17 00:00:00 2001
From: Tim Monko
Date: Tue, 19 May 2026 11:37:48 -0500
Subject: [PATCH 01/12] imrpove language of gallery landing page
---
docs/gallery.md | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/docs/gallery.md b/docs/gallery.md
index c14dfa8a4..961d8d394 100644
--- a/docs/gallery.md
+++ b/docs/gallery.md
@@ -4,9 +4,17 @@
Examples of napari usage.
-All examples in this gallery can be downloaded as Python scripts or Jupyter
-notebooks to be executed locally. Check out [](launch-jupyter) for more details
-on using napari in Jupyter notebooks.
+Each example page includes downloads for both the source `.py` file and a
+generated Jupyter notebook. If you download the Python script, you can run it
+with `napari path/to/my_example.py` or drag the `.py` file onto a running napari viewer
+to execute it there. Some examples may require additional dependencies beyond
+napari itself, so be sure to check the example's source code for any additional
+requirements if you see an error when trying to run it.
+Check out [](launch-jupyter) for more details on using
+napari in Jupyter notebooks.
+
+Use the tag index below to browse examples by topic, or jump straight into the
+gallery grid.
```{note}
From 431c86946be733389e7995c45e8e0c2bd8056f18 Mon Sep 17 00:00:00 2001
From: Tim Monko
Date: Tue, 19 May 2026 11:38:13 -0500
Subject: [PATCH 02/12] WIP initial LLM working strategy
---
docs/_static/custom.css | 27 +++++++++++++++++
docs/conf.py | 64 +++++++++++++++++++++++++++++++++++------
2 files changed, 82 insertions(+), 9 deletions(-)
diff --git a/docs/_static/custom.css b/docs/_static/custom.css
index e5c50e450..918057251 100644
--- a/docs/_static/custom.css
+++ b/docs/_static/custom.css
@@ -44,6 +44,33 @@ html[data-theme="dark"] div.sphx-glr-download a:hover {
background-color: #222832 !important;
}
+.napari-gallery-downloads {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ margin: 0 0 1.25rem;
+}
+
+.napari-gallery-downloads .sphx-glr-download {
+ margin: 0;
+}
+
+html[data-theme="light"] .napari-gallery-downloads div.sphx-glr-download a {
+ border-color: #d7deea !important;
+}
+
+html[data-theme="dark"] .napari-gallery-downloads div.sphx-glr-download a {
+ border-color: #4f5f79 !important;
+ color: #fff;
+}
+
+.sphx-glr-download-link-note,
+.sphx-glr-download-jupyter,
+.sphx-glr-download-python,
+.sphx-glr-download-zip {
+ display: none;
+}
+
/* Version warning banner color */
#bd-header-version-warning {
background-color: color-mix(in srgb, var(--pst-color-secondary-bg), transparent 30%);
diff --git a/docs/conf.py b/docs/conf.py
index 569b5b0da..362b9bf76 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -396,6 +396,60 @@ def napari_scraper(block, block_vars, gallery_conf):
return scrapers.figure_rst(img_paths, gallery_conf['src_dir'])
+GALLERY_DOWNLOAD_NOTE_RE = re.compile(
+ r'\n\.\. only:: html\n\n'
+ r' \.\. note::\n'
+ r' :class: sphx-glr-download-link-note\n\n'
+ r' :ref:`Go to the end ]+>`\n'
+ r' to download the full example as a Python script or as a\n'
+ r' Jupyter notebook\.\.?\n',
+ re.MULTILINE,
+)
+GALLERY_TITLE_RE = re.compile(r'^(?P.+\n=+\n)', re.MULTILINE)
+
+
+def add_gallery_download_buttons(app, docname, source):
+ """Add compact top-of-page download links to generated gallery examples."""
+ if not docname.startswith('gallery/'):
+ return
+ if docname.endswith('/index') or docname.endswith('sg_execution_times'):
+ return
+
+ content = source[0]
+ if 'THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.' not in content:
+ return
+
+ content = GALLERY_DOWNLOAD_NOTE_RE.sub('\n', content, count=1)
+ if 'napari-gallery-downloads' in content:
+ source[0] = content
+ return
+
+ example_name = Path(docname).name
+ title_match = GALLERY_TITLE_RE.search(content)
+ if title_match is None:
+ source[0] = content
+ return
+
+ download_block = f"""
+.. only:: html
+
+ .. container:: napari-gallery-downloads
+
+ .. container:: sphx-glr-download
+
+ :download:`Python (.py) <{example_name}.py>`
+
+ .. container:: sphx-glr-download
+
+ :download:`Notebook (.ipynb) <{example_name}.ipynb>`
+"""
+ source[0] = (
+ content[: title_match.end()]
+ + download_block
+ + content[title_match.end() :]
+ )
+
+
gen_rst.EXAMPLE_HEADER = """
.. DO NOT EDIT.
.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.
@@ -403,15 +457,6 @@ def napari_scraper(block, block_vars, gallery_conf):
.. "{0}"
.. LINE NUMBERS ARE GIVEN BELOW.
-.. only:: html
-
- .. note::
- :class: sphx-glr-download-link-note
-
- :ref:`Go to the end `
- to download the full example as a Python script or as a
- Jupyter notebook.{2}
-
.. rst-class:: sphx-glr-example-title
.. _sphx_glr_{1}:
@@ -549,6 +594,7 @@ def setup(app):
"""
app.registry.source_suffix.pop('.ipynb', None)
app.connect('source-read', add_google_calendar_secrets)
+ app.connect('source-read', add_gallery_download_buttons)
app.connect('linkcheck-process-uri', rewrite_github_anchor)
app.connect('autodoc-process-docstring', qt_docstrings)
From fc1477b4f50d9d120329e7389d4c873b2f1ce27f Mon Sep 17 00:00:00 2001
From: Tim Monko
Date: Tue, 19 May 2026 12:13:29 -0500
Subject: [PATCH 03/12] simplify imlpementation and css
---
docs/_static/custom.css | 9 ---------
docs/conf.py | 24 +++++++-----------------
2 files changed, 7 insertions(+), 26 deletions(-)
diff --git a/docs/_static/custom.css b/docs/_static/custom.css
index 918057251..0eed907b2 100644
--- a/docs/_static/custom.css
+++ b/docs/_static/custom.css
@@ -55,15 +55,6 @@ html[data-theme="dark"] div.sphx-glr-download a:hover {
margin: 0;
}
-html[data-theme="light"] .napari-gallery-downloads div.sphx-glr-download a {
- border-color: #d7deea !important;
-}
-
-html[data-theme="dark"] .napari-gallery-downloads div.sphx-glr-download a {
- border-color: #4f5f79 !important;
- color: #fff;
-}
-
.sphx-glr-download-link-note,
.sphx-glr-download-jupyter,
.sphx-glr-download-python,
diff --git a/docs/conf.py b/docs/conf.py
index 362b9bf76..43b7cdf72 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -396,20 +396,16 @@ def napari_scraper(block, block_vars, gallery_conf):
return scrapers.figure_rst(img_paths, gallery_conf['src_dir'])
-GALLERY_DOWNLOAD_NOTE_RE = re.compile(
- r'\n\.\. only:: html\n\n'
- r' \.\. note::\n'
- r' :class: sphx-glr-download-link-note\n\n'
- r' :ref:`Go to the end ]+>`\n'
- r' to download the full example as a Python script or as a\n'
- r' Jupyter notebook\.\.?\n',
- re.MULTILINE,
-)
-GALLERY_TITLE_RE = re.compile(r'^(?P.+\n=+\n)', re.MULTILINE)
+GALLERY_TITLE_RE = re.compile(r'^.+\n=+\n', re.MULTILINE)
def add_gallery_download_buttons(app, docname, source):
- """Add compact top-of-page download links to generated gallery examples."""
+ """Add compact top-of-page download links to generated gallery examples.
+
+ The title of the example is used to determine where to insert the download links,
+ which are only added to generated gallery examples.
+ The links are added after the title.
+ """
if not docname.startswith('gallery/'):
return
if docname.endswith('/index') or docname.endswith('sg_execution_times'):
@@ -419,15 +415,9 @@ def add_gallery_download_buttons(app, docname, source):
if 'THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.' not in content:
return
- content = GALLERY_DOWNLOAD_NOTE_RE.sub('\n', content, count=1)
- if 'napari-gallery-downloads' in content:
- source[0] = content
- return
-
example_name = Path(docname).name
title_match = GALLERY_TITLE_RE.search(content)
if title_match is None:
- source[0] = content
return
download_block = f"""
From 69a368b10abe3a4582b93223fe264a91bb109a94 Mon Sep 17 00:00:00 2001
From: Tim Monko
Date: Tue, 19 May 2026 12:40:13 -0500
Subject: [PATCH 04/12] merge in deps check branch
---
docs/conf.py | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 84 insertions(+)
diff --git a/docs/conf.py b/docs/conf.py
index 43b7cdf72..dc55bd919 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -9,6 +9,7 @@
# -- Imports --------------------------------------------------------------
+import ast
import logging
import json
import os
@@ -438,6 +439,56 @@ def add_gallery_download_buttons(app, docname, source):
+ download_block
+ content[title_match.end() :]
)
+# ---------------------------------------------------------------------------
+# Gallery extra-dependency detection
+# ---------------------------------------------------------------------------
+
+# Packages from napari's [dependency-groups] gallery that are NOT included in
+# a standard `napari[all]` install. Maps import-root -> pip package name.
+_GALLERY_EXTRAS: dict[str, str] = {
+ 'glasbey': 'glasbey',
+ 'matplotlib': 'matplotlib',
+ 'meshio': 'meshio',
+ 'nilearn': 'nilearn',
+ 'trackastra': 'trackastra',
+ 'xarray': 'xarray',
+}
+
+# Matches the source .py path embedded in every sphinx-gallery RST header.
+_GALLERY_SOURCE_PATH_RE = re.compile(r'\.\. "(.+\.py)"\n')
+
+# Matches the first top-level RST title (text + === underline).
+_GALLERY_TITLE_RE = re.compile(r'^.+\n=+\n', re.MULTILINE)
+
+
+def _get_example_extra_deps(content: str, srcdir: str) -> list[str]:
+ """Return extra pip packages needed by a gallery example beyond napari[all]."""
+ path_match = _GALLERY_SOURCE_PATH_RE.search(content)
+ if path_match is None:
+ return []
+ py_path = Path(path_match.group(1))
+ if not py_path.is_absolute():
+ py_path = Path(srcdir) / py_path
+ if not py_path.exists():
+ return []
+ try:
+ tree = ast.parse(py_path.read_text(encoding='utf-8', errors='ignore'))
+ except SyntaxError:
+ return []
+ seen: set[str] = set()
+ needed: list[str] = []
+ for node in ast.walk(tree):
+ if isinstance(node, ast.Import):
+ roots = {alias.name.split('.')[0] for alias in node.names}
+ elif isinstance(node, ast.ImportFrom) and node.module:
+ roots = {node.module.split('.')[0]}
+ else:
+ continue
+ for root in roots:
+ if (pkg := _GALLERY_EXTRAS.get(root)) and pkg not in seen:
+ seen.add(pkg)
+ needed.append(pkg)
+ return needed
gen_rst.EXAMPLE_HEADER = """
@@ -571,6 +622,38 @@ def qt_docstrings(app, what, name, obj, options, lines):
# -- Docs build setup ------------------------------------------------------
+def add_gallery_extra_deps_notice(app, docname, source):
+ """Inject an extra-dependencies notice beneath each gallery example title.
+
+ Only fires for examples that import packages beyond ``napari[all]``.
+ The packages are detected by scanning the source ``.py`` file via AST,
+ comparing against :data:`_GALLERY_EXTRAS`.
+ """
+ if not docname.startswith('gallery/') or docname.endswith('/index'):
+ return
+ content = source[0]
+ if 'THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.' not in content:
+ return
+ extra_deps = _get_example_extra_deps(content, app.srcdir)
+ if not extra_deps:
+ return
+ title_match = _GALLERY_TITLE_RE.search(content)
+ if title_match is None:
+ return
+ deps_str = ' '.join(f'``{d}``' for d in extra_deps)
+ install_str = ' '.join(extra_deps)
+ notice = f"""
+.. only:: html
+
+ .. container:: napari-gallery-extra-deps
+
+ **Extra dependencies required:** {deps_str}
+ — install with: ``pip install {install_str}``
+
+"""
+ source[0] = content[: title_match.end()] + notice + content[title_match.end() :]
+
+
def setup(app):
"""Set up docs build.
@@ -585,6 +668,7 @@ def setup(app):
app.registry.source_suffix.pop('.ipynb', None)
app.connect('source-read', add_google_calendar_secrets)
app.connect('source-read', add_gallery_download_buttons)
+ app.connect('source-read', add_gallery_extra_deps_notice)
app.connect('linkcheck-process-uri', rewrite_github_anchor)
app.connect('autodoc-process-docstring', qt_docstrings)
From 8d3cd9aedff282264738d4f34a3651170b7bb421 Mon Sep 17 00:00:00 2001
From: Tim Monko
Date: Tue, 19 May 2026 14:01:48 -0500
Subject: [PATCH 05/12] abstraction and addition of dep check
---
docs/conf.py | 251 ++++++++++++++++++++++++++++++---------------------
1 file changed, 150 insertions(+), 101 deletions(-)
diff --git a/docs/conf.py b/docs/conf.py
index dc55bd919..491f887f3 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -10,19 +10,27 @@
# -- Imports --------------------------------------------------------------
import ast
-import logging
import json
+import logging
import os
import re
+import tomllib
from datetime import datetime
+from functools import cache
from importlib import import_module
-from importlib.metadata import distribution, version as metadata_version
+from importlib.metadata import (
+ distribution,
+ packages_distributions,
+ version as metadata_version,
+)
from pathlib import Path
from urllib.parse import urlparse, urlunparse
import napari
from jinja2.filters import FILTERS
from napari._version import __version_tuple__
+from packaging.requirements import Requirement
+from packaging.utils import canonicalize_name
from packaging.version import parse as parse_version
from pygments.lexers.configs import TOMLLexer
from sphinx.highlighting import lexers
@@ -397,86 +405,102 @@ def napari_scraper(block, block_vars, gallery_conf):
return scrapers.figure_rst(img_paths, gallery_conf['src_dir'])
+GALLERY_SOURCE_PATH_RE = re.compile(r'\.\. "(.+\.py)"\n')
GALLERY_TITLE_RE = re.compile(r'^.+\n=+\n', re.MULTILINE)
-
-
-def add_gallery_download_buttons(app, docname, source):
- """Add compact top-of-page download links to generated gallery examples.
-
- The title of the example is used to determine where to insert the download links,
- which are only added to generated gallery examples.
- The links are added after the title.
- """
- if not docname.startswith('gallery/'):
- return
- if docname.endswith('/index') or docname.endswith('sg_execution_times'):
- return
-
- content = source[0]
- if 'THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.' not in content:
- return
-
- example_name = Path(docname).name
- title_match = GALLERY_TITLE_RE.search(content)
- if title_match is None:
- return
-
- download_block = f"""
-.. only:: html
-
- .. container:: napari-gallery-downloads
-
- .. container:: sphx-glr-download
-
- :download:`Python (.py) <{example_name}.py>`
-
- .. container:: sphx-glr-download
-
- :download:`Notebook (.ipynb) <{example_name}.ipynb>`
-"""
- source[0] = (
- content[: title_match.end()]
- + download_block
- + content[title_match.end() :]
+NAPARI_PYPROJECT = Path(__file__).resolve().parents[2] / 'napari' / 'pyproject.toml'
+PLUGIN_INSTALL_GUIDE = '../plugins/start_using_plugins/finding_and_installing_plugins'
+GALLERY_IGNORED_PACKAGES = frozenset({'pooch'})
+
+
+def _marker_matches(requirement: Requirement) -> bool:
+ return requirement.marker is None or requirement.marker.evaluate()
+
+
+@cache
+def _load_napari_pyproject() -> dict:
+ with NAPARI_PYPROJECT.open('rb') as file:
+ return tomllib.load(file)
+
+
+@cache
+def _get_napari_declared_packages(extra: str | None = None) -> frozenset[str]:
+ project = _load_napari_pyproject()['project']
+ packages = {
+ canonicalize_name(requirement.name)
+ for requirement in map(Requirement, project['dependencies'])
+ if _marker_matches(requirement)
+ }
+ if extra is None:
+ return frozenset(packages)
+
+ pending = [extra]
+ seen_extras = set()
+ optional_dependencies = project['optional-dependencies']
+ while pending:
+ current_extra = pending.pop()
+ if current_extra in seen_extras:
+ continue
+ seen_extras.add(current_extra)
+ for requirement in map(Requirement, optional_dependencies[current_extra]):
+ if not _marker_matches(requirement):
+ continue
+ if canonicalize_name(requirement.name) == 'napari' and requirement.extras:
+ pending.extend(sorted(requirement.extras))
+ continue
+ packages.add(canonicalize_name(requirement.name))
+
+ return frozenset(packages)
+
+
+@cache
+def _get_gallery_extra_packages() -> frozenset[str]:
+ optional_dependencies = _load_napari_pyproject()['project']['optional-dependencies']
+ gallery_packages = {
+ canonicalize_name(requirement.name)
+ for requirement in map(Requirement, optional_dependencies['gallery'])
+ if _marker_matches(requirement)
+ }
+ return frozenset(
+ sorted(
+ gallery_packages
+ - _get_napari_declared_packages('all')
+ - GALLERY_IGNORED_PACKAGES
+ )
)
-# ---------------------------------------------------------------------------
-# Gallery extra-dependency detection
-# ---------------------------------------------------------------------------
-
-# Packages from napari's [dependency-groups] gallery that are NOT included in
-# a standard `napari[all]` install. Maps import-root -> pip package name.
-_GALLERY_EXTRAS: dict[str, str] = {
- 'glasbey': 'glasbey',
- 'matplotlib': 'matplotlib',
- 'meshio': 'meshio',
- 'nilearn': 'nilearn',
- 'trackastra': 'trackastra',
- 'xarray': 'xarray',
-}
-# Matches the source .py path embedded in every sphinx-gallery RST header.
-_GALLERY_SOURCE_PATH_RE = re.compile(r'\.\. "(.+\.py)"\n')
-# Matches the first top-level RST title (text + === underline).
-_GALLERY_TITLE_RE = re.compile(r'^.+\n=+\n', re.MULTILINE)
+@cache
+def _get_gallery_extra_import_roots() -> dict[str, str]:
+ gallery_packages = _get_gallery_extra_packages()
+ import_roots: dict[str, str] = {}
+ for module_name, distributions in packages_distributions().items():
+ for dist_name in distributions:
+ package_name = canonicalize_name(dist_name)
+ if package_name in gallery_packages:
+ import_roots[module_name] = package_name
+ break
+ return import_roots
def _get_example_extra_deps(content: str, srcdir: str) -> list[str]:
- """Return extra pip packages needed by a gallery example beyond napari[all]."""
- path_match = _GALLERY_SOURCE_PATH_RE.search(content)
+ path_match = GALLERY_SOURCE_PATH_RE.search(content)
if path_match is None:
return []
+
py_path = Path(path_match.group(1))
if not py_path.is_absolute():
py_path = Path(srcdir) / py_path
if not py_path.exists():
return []
+
try:
tree = ast.parse(py_path.read_text(encoding='utf-8', errors='ignore'))
except SyntaxError:
return []
+
+ import_roots = _get_gallery_extra_import_roots()
+ extra_deps: list[str] = []
seen: set[str] = set()
- needed: list[str] = []
for node in ast.walk(tree):
if isinstance(node, ast.Import):
roots = {alias.name.split('.')[0] for alias in node.names}
@@ -484,11 +508,69 @@ def _get_example_extra_deps(content: str, srcdir: str) -> list[str]:
roots = {node.module.split('.')[0]}
else:
continue
+
for root in roots:
- if (pkg := _GALLERY_EXTRAS.get(root)) and pkg not in seen:
- seen.add(pkg)
- needed.append(pkg)
- return needed
+ package_name = import_roots.get(root)
+ if package_name and package_name not in seen:
+ seen.add(package_name)
+ extra_deps.append(package_name)
+
+ return extra_deps
+
+
+def _gallery_download_block(example_name: str) -> str:
+ return f"""
+.. only:: html
+
+ .. container:: napari-gallery-downloads
+
+ .. container:: sphx-glr-download
+
+ :download:`Python (.py) <{example_name}.py>`
+
+ .. container:: sphx-glr-download
+
+ :download:`Notebook (.ipynb) <{example_name}.ipynb>`
+"""
+
+
+def _gallery_extra_deps_note(extra_deps: list[str]) -> str:
+ deps = ', '.join(f'``{package}``' for package in extra_deps)
+ return f"""
+.. admonition:: Extra packages required
+
+This example requires additional packages that are not available in typical
+napari installations. For this example to work, you will need to install: {deps}.
+See :doc:`the installation guide <{PLUGIN_INSTALL_GUIDE}>` for ways to install
+additional packages, including from within napari.
+"""
+
+
+def augment_gallery_example(app, docname, source):
+ """Add shared top-of-page UI for generated gallery example pages."""
+ if not docname.startswith('gallery/'):
+ return
+ if docname.endswith('/index') or docname.endswith('sg_execution_times'):
+ return
+
+ content = source[0]
+ if 'THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.' not in content:
+ return
+
+ title_match = GALLERY_TITLE_RE.search(content)
+ if title_match is None:
+ return
+
+ blocks = [_gallery_download_block(Path(docname).name)]
+ extra_deps = _get_example_extra_deps(content, app.srcdir)
+ if extra_deps:
+ blocks.append(_gallery_extra_deps_note(extra_deps))
+
+ source[0] = (
+ content[: title_match.end()]
+ + '\n'.join(blocks)
+ + content[title_match.end() :]
+ )
gen_rst.EXAMPLE_HEADER = """
@@ -622,38 +704,6 @@ def qt_docstrings(app, what, name, obj, options, lines):
# -- Docs build setup ------------------------------------------------------
-def add_gallery_extra_deps_notice(app, docname, source):
- """Inject an extra-dependencies notice beneath each gallery example title.
-
- Only fires for examples that import packages beyond ``napari[all]``.
- The packages are detected by scanning the source ``.py`` file via AST,
- comparing against :data:`_GALLERY_EXTRAS`.
- """
- if not docname.startswith('gallery/') or docname.endswith('/index'):
- return
- content = source[0]
- if 'THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.' not in content:
- return
- extra_deps = _get_example_extra_deps(content, app.srcdir)
- if not extra_deps:
- return
- title_match = _GALLERY_TITLE_RE.search(content)
- if title_match is None:
- return
- deps_str = ' '.join(f'``{d}``' for d in extra_deps)
- install_str = ' '.join(extra_deps)
- notice = f"""
-.. only:: html
-
- .. container:: napari-gallery-extra-deps
-
- **Extra dependencies required:** {deps_str}
- — install with: ``pip install {install_str}``
-
-"""
- source[0] = content[: title_match.end()] + notice + content[title_match.end() :]
-
-
def setup(app):
"""Set up docs build.
@@ -667,8 +717,7 @@ def setup(app):
"""
app.registry.source_suffix.pop('.ipynb', None)
app.connect('source-read', add_google_calendar_secrets)
- app.connect('source-read', add_gallery_download_buttons)
- app.connect('source-read', add_gallery_extra_deps_notice)
+ app.connect('source-read', augment_gallery_example)
app.connect('linkcheck-process-uri', rewrite_github_anchor)
app.connect('autodoc-process-docstring', qt_docstrings)
From 377563f14faf16ea555293fabed342ea15186eb8 Mon Sep 17 00:00:00 2001
From: Tim Monko
Date: Tue, 19 May 2026 14:10:21 -0500
Subject: [PATCH 06/12] fix deps admonition
---
docs/conf.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/conf.py b/docs/conf.py
index 491f887f3..a83a74799 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -539,10 +539,10 @@ def _gallery_extra_deps_note(extra_deps: list[str]) -> str:
return f"""
.. admonition:: Extra packages required
-This example requires additional packages that are not available in typical
-napari installations. For this example to work, you will need to install: {deps}.
-See :doc:`the installation guide <{PLUGIN_INSTALL_GUIDE}>` for ways to install
-additional packages, including from within napari.
+ This example requires additional packages that are not available in typical
+ napari installations. For this example to work, you will need to install: {deps}.
+ See :doc:`the installation guide <{PLUGIN_INSTALL_GUIDE}>` for ways to install
+ additional packages, including from within napari.
"""
From 80e2ab2d9f8a987de81121052032429a7cc4ce92 Mon Sep 17 00:00:00 2001
From: Tim Monko
Date: Tue, 19 May 2026 14:38:01 -0500
Subject: [PATCH 07/12] strengthen deps admonition help
---
docs/conf.py | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/docs/conf.py b/docs/conf.py
index a83a74799..64547dbe3 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -409,6 +409,7 @@ def napari_scraper(block, block_vars, gallery_conf):
GALLERY_TITLE_RE = re.compile(r'^.+\n=+\n', re.MULTILINE)
NAPARI_PYPROJECT = Path(__file__).resolve().parents[2] / 'napari' / 'pyproject.toml'
PLUGIN_INSTALL_GUIDE = '../plugins/start_using_plugins/finding_and_installing_plugins'
+NAPARI_INSTALL_GUIDE = '../getting_started/installation'
GALLERY_IGNORED_PACKAGES = frozenset({'pooch'})
@@ -540,9 +541,11 @@ def _gallery_extra_deps_note(extra_deps: list[str]) -> str:
.. admonition:: Extra packages required
This example requires additional packages that are not available in typical
- napari installations. For this example to work, you will need to install: {deps}.
- See :doc:`the installation guide <{PLUGIN_INSTALL_GUIDE}>` for ways to install
- additional packages, including from within napari.
+ napari installations.
+ For this example to work in :doc:`recommended napari installations <{NAPARI_INSTALL_GUIDE}>`,
+ you will need to install additional packages: {deps}.
+ See the :doc:`the plugin installation guide <{PLUGIN_INSTALL_GUIDE}>`
+ for ways to install additional packages from within napari.
"""
From ac55171be973e322daa63a85ce998dc8acf34daf Mon Sep 17 00:00:00 2001
From: Tim Monko
Date: Wed, 20 May 2026 15:18:32 -0500
Subject: [PATCH 08/12] sync css with #1021
---
docs/_static/custom.css | 140 ++++++++++++++++++++--------------------
1 file changed, 71 insertions(+), 69 deletions(-)
diff --git a/docs/_static/custom.css b/docs/_static/custom.css
index 0eed907b2..0070f87e3 100644
--- a/docs/_static/custom.css
+++ b/docs/_static/custom.css
@@ -1,60 +1,45 @@
-/*
-Sphinx-Gallery has compatible CSS to fix default sphinx themes
-Tested for Sphinx 1.3.1 for all themes: default, alabaster, sphinxdoc,
-scrolls, agogo, traditional, nature, haiku, pyramid
-Tested for Read the Docs theme 0.1.7 */
-
-html[data-theme="light"] div.sphx-glr-download a {
- background-color: rgb(255, 255, 255) !important;
- background-image: linear-gradient(to bottom, rgb(255, 255, 255), #ffffff) !important;
+/* ── Buttons for homepage and gallery ───────────────────────────────────── */
+
+.button-primary,
+.button-alt,
+.napari-gallery-downloads .sphx-glr-download a {
+ background-image: none !important;
border-radius: 4px;
- border: 1px solid #ffffff !important;
- color: #000;
- display: inline-block;
- font-weight: bold;
- padding: 1ex;
text-align: center;
+ text-decoration: none;
}
-html[data-theme="light"] div.sphx-glr-download a:hover {
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1),
- 0 1px 5px rgba(0, 0, 0, 0.25);
- text-decoration: none;
- background-image: none;
- background-color: #ffffff !important;
+/* makes homepage button take up width of column */
+.button-primary {
+ display: block;
}
-html[data-theme="dark"] div.sphx-glr-download a {
- background-color: #222832 !important;
- background-image: linear-gradient(to bottom, #222832, #222832) !important;
- border-radius: 4px;
- border: 1px solid #222832 !important;
- color: #000;
+/* Allow border override to show through on example buttons */
+.napari-gallery-downloads .sphx-glr-download a {
+ border: 1px solid transparent;
display: inline-block;
- font-weight: bold;
- padding: 1ex;
- text-align: center;
+ padding: 0.45rem 0.8rem;
}
-html[data-theme="dark"] div.sphx-glr-download a:hover {
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1),
- 0 1px 5px rgba(0, 0, 0, 0.25);
- text-decoration: none;
- background-image: none;
- background-color: #222832 !important;
+/* force sphinx-gallery download code to inherit text color */
+.napari-gallery-downloads .sphx-glr-download a code.download {
+ color: inherit !important;
}
+/* horizontally align and reduce lower margin of container */
.napari-gallery-downloads {
display: flex;
flex-wrap: wrap;
- gap: 0.5rem;
- margin: 0 0 1.25rem;
+ gap: 0.375rem;
+ margin: 0 0 0.35rem;
}
+/* remove spacing around download buttons */
.napari-gallery-downloads .sphx-glr-download {
margin: 0;
}
+/* Remove the sphinx-gallery downloads note and buttons */
.sphx-glr-download-link-note,
.sphx-glr-download-jupyter,
.sphx-glr-download-python,
@@ -62,53 +47,75 @@ html[data-theme="dark"] div.sphx-glr-download a:hover {
display: none;
}
-/* Version warning banner color */
-#bd-header-version-warning {
- background-color: color-mix(in srgb, var(--pst-color-secondary-bg), transparent 30%);
+html[data-theme="light"] .button-primary,
+html[data-theme="light"] .napari-gallery-downloads .sphx-glr-download a {
+ background-color: var(--napari-primary-blue) !important;
+ border-color: var(--napari-primary-blue) !important;
+ color: var(--napari-color-text-base) !important;
}
-#bd-header-version-warning .pst-button-link-to-stable-version {
- background-color: color-mix(in srgb, var(--pst-color-secondary-bg), transparent 0%);
- border-color: var(--pst-color-secondary-bg);
- color: var(--napari-color-text-base);
- font-weight: 700;
+html[data-theme="dark"] .button-primary,
+html[data-theme="dark"] .napari-gallery-downloads .sphx-glr-download a {
+ background-color: var(--napari-purple) !important;
+ border-color: var(--napari-purple) !important;
+ color: var(--napari-color-text-base) !important;
}
-#bd-header-version-warning .pst-button-link-to-stable-version:hover {
- background-color: var(--pst-color-secondary-bg);
- border-color: var(--pst-color-secondary-bg);
- color: var(--napari-color-text-base);
- font-weight: 700;
+html[data-theme="light"] .button-primary:hover,
+html[data-theme="light"] .napari-gallery-downloads .sphx-glr-download a:hover {
+ background-color: color-mix(in srgb, var(--napari-primary-blue), transparent 25%) !important;
+ text-decoration: underline;
}
-/* ── Homepage accent cards ───────────────────────────────────────────
- Install, Download cards with theme-aware napari blue.
- sphinx-design marks every property !important, so we must too. */
-
-.homepage-button {
- display: block;
+html[data-theme="dark"] .button-primary:hover,
+html[data-theme="dark"] .napari-gallery-downloads .sphx-glr-download a:hover {
+ background-color: color-mix(in srgb, var(--napari-purple), transparent 25%) !important;
+ text-decoration: underline;
}
-html[data-theme="light"] .homepage-button {
- background-color: var(--napari-primary-blue) !important;
+html[data-theme="light"] .button-alt {
+ background-color: var(--pst-color-background) !important;
border-color: var(--napari-primary-blue) !important;
color: var(--napari-color-text-base) !important;
}
-html[data-theme="dark"] .homepage-button {
- background-color: var(--napari-purple) !important;
+html[data-theme="dark"] .button-alt {
+ background-color: var(--pst-color-background) !important;
border-color: var(--napari-purple) !important;
color: var(--napari-color-text-base) !important;
}
-html[data-theme="light"] .homepage-button:hover {
+html[data-theme="light"] .button-alt:hover {
+ background-color: var(--pst-color-background) !important;
+ border-color: var(--napari-primary-blue) !important;
+ color: var(--napari-color-text-base) !important;
text-decoration: underline;
- background-color: color-mix(in srgb, var(--napari-primary-blue), transparent 25%) !important;
}
-html[data-theme="dark"] .homepage-button:hover {
+html[data-theme="dark"] .button-alt:hover {
+ background-color: var(--pst-color-background) !important;
+ border-color: var(--napari-purple) !important;
+ color: var(--napari-color-text-base) !important;
text-decoration: underline;
- background-color: color-mix(in srgb, var(--napari-purple), transparent 25%) !important;
+}
+
+/* Version warning banner color */
+#bd-header-version-warning {
+ background-color: color-mix(in srgb, var(--pst-color-secondary-bg), transparent 30%);
+}
+
+#bd-header-version-warning .pst-button-link-to-stable-version {
+ background-color: color-mix(in srgb, var(--pst-color-secondary-bg), transparent 0%);
+ border-color: var(--pst-color-secondary-bg);
+ color: var(--napari-color-text-base);
+ font-weight: 700;
+}
+
+#bd-header-version-warning .pst-button-link-to-stable-version:hover {
+ background-color: var(--pst-color-secondary-bg);
+ border-color: var(--pst-color-secondary-bg);
+ color: var(--napari-color-text-base);
+ font-weight: 700;
}
/* ── Homepage quicklinks (icon columns) ──────────────────────────────── */
@@ -241,8 +248,3 @@ html[data-theme="dark"] .homepage-quicklinks a {
html[data-theme="dark"] img#homepage-featured-example-image {
background-color: var(--pst-color-background) !important;
}
-
-.homepage-example-button:hover {
- background-color: var(--pst-color-background) !important;
- color: var(--napari-color-text-base) !important;
-}
\ No newline at end of file
From b70ade095b3b42bc3a9ff785314fbb761205908b Mon Sep 17 00:00:00 2001
From: Tim Monko
Date: Wed, 20 May 2026 15:21:30 -0500
Subject: [PATCH 09/12] sync index.md
---
docs/index.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/index.md b/docs/index.md
index b775ffabb..aa3935234 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -64,7 +64,7 @@ theme:
:color: primary
:ref-type: myst
:shadow:
-:class: homepage-button
+:class: button-primary
{material-regular}`arrow_forward;1.2em` **Get started**
```
@@ -133,8 +133,8 @@ A standalone installer for when you want napari without setting up Python first.
Display an image in napari and explore the viewer with a minimal example.
-
Examples gallery
-
+
Examples gallery
+
From c7aac0e8acbe5faca128a1b6ea5d2703e0d47791 Mon Sep 17 00:00:00 2001
From: Tim Monko
Date: Tue, 26 May 2026 23:34:44 -0500
Subject: [PATCH 10/12] sync with #1021
---
docs/conf.py | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/docs/conf.py b/docs/conf.py
index 64547dbe3..665fb6358 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -406,7 +406,9 @@ def napari_scraper(block, block_vars, gallery_conf):
GALLERY_SOURCE_PATH_RE = re.compile(r'\.\. "(.+\.py)"\n')
-GALLERY_TITLE_RE = re.compile(r'^.+\n=+\n', re.MULTILINE)
+GALLERY_METADATA_END_RE = re.compile(
+ r'^\.\. GENERATED FROM PYTHON SOURCE LINES \d+-\d+\s*$', re.MULTILINE
+)
NAPARI_PYPROJECT = Path(__file__).resolve().parents[2] / 'napari' / 'pyproject.toml'
PLUGIN_INSTALL_GUIDE = '../plugins/start_using_plugins/finding_and_installing_plugins'
NAPARI_INSTALL_GUIDE = '../getting_started/installation'
@@ -550,7 +552,13 @@ def _gallery_extra_deps_note(extra_deps: list[str]) -> str:
def augment_gallery_example(app, docname, source):
- """Add shared top-of-page UI for generated gallery example pages."""
+ """Add compact download links and admontion before script.
+
+ Sphinx-Gallery emits the example title first, followed by the short
+ description and tags, and only then the ``GENERATED FROM PYTHON SOURCE``
+ marker. Insert the links just before that first marker so they stay below
+ the description metadata without overriding Sphinx-Gallery templates.
+ """
if not docname.startswith('gallery/'):
return
if docname.endswith('/index') or docname.endswith('sg_execution_times'):
@@ -560,8 +568,8 @@ def augment_gallery_example(app, docname, source):
if 'THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.' not in content:
return
- title_match = GALLERY_TITLE_RE.search(content)
- if title_match is None:
+ metadata_end_match = GALLERY_METADATA_END_RE.search(content)
+ if metadata_end_match is None:
return
blocks = [_gallery_download_block(Path(docname).name)]
@@ -570,9 +578,9 @@ def augment_gallery_example(app, docname, source):
blocks.append(_gallery_extra_deps_note(extra_deps))
source[0] = (
- content[: title_match.end()]
+ content[: metadata_end_match.start()]
+ '\n'.join(blocks)
- + content[title_match.end() :]
+ + content[metadata_end_match.start() :]
)
From bc68729dbfac143b79f12f201dc808a95dd79970 Mon Sep 17 00:00:00 2001
From: Tim Monko
Date: Tue, 26 May 2026 23:55:21 -0500
Subject: [PATCH 11/12] point install help lnik to napari-plugin-manager docs
---
docs/conf.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/docs/conf.py b/docs/conf.py
index 665fb6358..49636aa5d 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -410,7 +410,9 @@ def napari_scraper(block, block_vars, gallery_conf):
r'^\.\. GENERATED FROM PYTHON SOURCE LINES \d+-\d+\s*$', re.MULTILINE
)
NAPARI_PYPROJECT = Path(__file__).resolve().parents[2] / 'napari' / 'pyproject.toml'
-PLUGIN_INSTALL_GUIDE = '../plugins/start_using_plugins/finding_and_installing_plugins'
+PLUGIN_MANAGER_ADDITIONAL_PACKAGES_GUIDE = (
+ 'https://napari.org/napari-plugin-manager/#installing-via-direct-entry'
+)
NAPARI_INSTALL_GUIDE = '../getting_started/installation'
GALLERY_IGNORED_PACKAGES = frozenset({'pooch'})
@@ -546,7 +548,7 @@ def _gallery_extra_deps_note(extra_deps: list[str]) -> str:
napari installations.
For this example to work in :doc:`recommended napari installations <{NAPARI_INSTALL_GUIDE}>`,
you will need to install additional packages: {deps}.
- See the :doc:`the plugin installation guide <{PLUGIN_INSTALL_GUIDE}>`
+ See the `plugin manager guide on installing additional packages <{PLUGIN_MANAGER_ADDITIONAL_PACKAGES_GUIDE}>`
for ways to install additional packages from within napari.
"""
From 6153ed6b0367347bf08e85be928a6a3891bf86cc Mon Sep 17 00:00:00 2001
From: Tim Monko
Date: Wed, 27 May 2026 00:01:18 -0500
Subject: [PATCH 12/12] fix link to plugin manager guide rendering
---
docs/conf.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/conf.py b/docs/conf.py
index 49636aa5d..2c7bd2e50 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -548,7 +548,7 @@ def _gallery_extra_deps_note(extra_deps: list[str]) -> str:
napari installations.
For this example to work in :doc:`recommended napari installations <{NAPARI_INSTALL_GUIDE}>`,
you will need to install additional packages: {deps}.
- See the `plugin manager guide on installing additional packages <{PLUGIN_MANAGER_ADDITIONAL_PACKAGES_GUIDE}>`
+ See the `plugin manager guide <{PLUGIN_MANAGER_ADDITIONAL_PACKAGES_GUIDE}>`_
for ways to install additional packages from within napari.
"""