Coverage for ase / utils / sphinx_assert.py: 28.57%
28 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-21 15:52 +0000
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-21 15:52 +0000
1"""Conditional execution of sphinx-gallery scripts via companion probe files.
3Overview
4--------
5The purpose of this module is for sphinx gallery to optionally generate certain
6tutorials. This means the docs can be built without all the DFT codes installed.
7If sphinx-gallery one day makes this possible via configuration (e.g. "optional"
8tutorials or better code injection around examples), then this module can be
9deleted.
11Probe file convention
12---------------------
13For every gallery script ``plot_something.py``, an optional companion file
14``plot_something_prerequisites.py`` may exist in the same directory. This file
15contains code that raises the ``SphinxPrerequisitesError`` defined here if the
16requirements for the execution of the actual gallery script are not met::
18 from ase.utils.sphinx_assert impoty SphinxPrerequisitesError
19 try:
20 import gpaw
21 except ImportError as e:
22 raise SphinxPrerequisitesError from e
24How it works
25------------
26The module monkey-patches ``gen_rst.execute_script`` from sphinx-gallery.
27Before each script runs, the patch looks for a companion probe file next to
28the script and tries to import it. A failed import is caught and reported as
29a descriptive warning, and ``script_vars['execute_script']`` is set to False
30so sphinx-gallery renders the code cells without executing them.
31"""
33import importlib
34import warnings
35from pathlib import Path
37PROBE_SUFFIX = '_prerequisites'
40class SphinxPrerequisitesError(Exception):
41 """Raised by a probe file to signal that its gallery script should be
42 skipped.
44 Raise this when a required dependency is missing or incompatible. Any other
45 exception raised by a probe file is treated as an unexpected error and
46 propagated normally.
47 """
50def probe_path(script_path):
51 """Return the Path of the companion probe file for *script_path*.
53 E.g. ``examples/**/plot_something.py`` ->
54 ``examples/**/plot_something_prerequisites.py``.
55 """
56 p = Path(script_path)
57 return p.with_stem(p.stem + PROBE_SUFFIX)
60def run_probe(script_path):
61 """Run the probe file for *script_path*, if one exists.
63 The probe is imported in a fresh empty namespace so its side effects do
64 not leak into the gallery environment. If the import fails with
65 SphinxPrerequisitesError the gallery script should be skipped.
67 Returns True if the probe imported successfully or no probe file exists.
68 Returns False on SphinxPrerequisitesError in import.
69 """
70 probe = probe_path(script_path)
71 if not probe.is_file():
72 return True
74 spec = importlib.util.spec_from_file_location('probe', probe)
75 mod = importlib.util.module_from_spec(spec)
76 try:
77 spec.loader.exec_module(mod)
78 except SphinxPrerequisitesError:
79 warnings.warn(
80 f'Probe {str(probe)!r} failed — '
81 f'{str(script_path)!r} will be shown but NOT executed.'
82 )
83 return False
85 return True
88def setup(app):
89 import sphinx_gallery.gen_rst as gen_rst
91 original_execute_script = gen_rst.execute_script
93 def patched_execute_script(script_blocks, script_vars, *args, **kwargs):
94 """Wrap ``execute_script`` to skip execution when the probe file
95 fails."""
97 if not run_probe(script_vars['src_file']):
98 script_vars['execute_script'] = False
99 return original_execute_script(
100 script_blocks, script_vars, *args, **kwargs
101 )
103 gen_rst.execute_script = patched_execute_script