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

1"""Conditional execution of sphinx-gallery scripts via companion probe files. 

2 

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. 

10 

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:: 

17 

18 from ase.utils.sphinx_assert impoty SphinxPrerequisitesError 

19 try: 

20 import gpaw 

21 except ImportError as e: 

22 raise SphinxPrerequisitesError from e 

23 

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""" 

32 

33import importlib 

34import warnings 

35from pathlib import Path 

36 

37PROBE_SUFFIX = '_prerequisites' 

38 

39 

40class SphinxPrerequisitesError(Exception): 

41 """Raised by a probe file to signal that its gallery script should be 

42 skipped. 

43 

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 """ 

48 

49 

50def probe_path(script_path): 

51 """Return the Path of the companion probe file for *script_path*. 

52 

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) 

58 

59 

60def run_probe(script_path): 

61 """Run the probe file for *script_path*, if one exists. 

62 

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. 

66 

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 

73 

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 

84 

85 return True 

86 

87 

88def setup(app): 

89 import sphinx_gallery.gen_rst as gen_rst 

90 

91 original_execute_script = gen_rst.execute_script 

92 

93 def patched_execute_script(script_blocks, script_vars, *args, **kwargs): 

94 """Wrap ``execute_script`` to skip execution when the probe file 

95 fails.""" 

96 

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 ) 

102 

103 gen_rst.execute_script = patched_execute_script