swh:1:snp:4e3e7077647a709f15b8c1b32ce7100175d0580b
Tip revision: 6b5a5fe85b64da3ca5e4c383c8f7b7b475fdfb48 authored by Jean Kossaifi on 15 October 2020, 18:31:27 UTC
Update website + API fix
Update website + API fix
Tip revision: 6b5a5fe
gen_gallery.py
# -*- coding: utf-8 -*-
# Author: Óscar Nájera
# License: 3-clause BSD
"""
Sphinx-Gallery Generator
========================
Attaches Sphinx-Gallery to Sphinx in order to generate the galleries
when building the documentation.
"""
from __future__ import division, print_function, absolute_import
import codecs
import copy
import re
import os
from . import glr_path_static
from . import sphinx_compatibility
from .gen_rst import generate_dir_rst, SPHX_GLR_SIG
from .docs_resolv import embed_code_links
from .downloads import generate_zipfiles
try:
FileNotFoundError
except NameError:
# Python2
FileNotFoundError = IOError
DEFAULT_GALLERY_CONF = {
'filename_pattern': re.escape(os.sep) + 'plot',
'examples_dirs': os.path.join('..', 'examples'),
'subsection_order': None,
'gallery_dirs': 'auto_examples',
'backreferences_dir': None,
'doc_module': (),
'reference_url': {},
# build options
'plot_gallery': True,
'download_all_examples': True,
'abort_on_example_error': False,
'failing_examples': {},
'expected_failing_examples': set(),
}
logger = sphinx_compatibility.getLogger('sphinx-gallery')
def clean_gallery_out(build_dir):
"""Deletes images under the sphx_glr namespace in the build directory"""
# Sphinx hack: sphinx copies generated images to the build directory
# each time the docs are made. If the desired image name already
# exists, it appends a digit to prevent overwrites. The problem is,
# the directory is never cleared. This means that each time you build
# the docs, the number of images in the directory grows.
#
# This question has been asked on the sphinx development list, but there
# was no response: https://git.net/ml/sphinx-dev/2011-02/msg00123.html
#
# The following is a hack that prevents this behavior by clearing the
# image build directory from gallery images each time the docs are built.
# If sphinx changes their layout between versions, this will not
# work (though it should probably not cause a crash).
# Tested successfully on Sphinx 1.0.7
build_image_dir = os.path.join(build_dir, '_images')
if os.path.exists(build_image_dir):
filelist = os.listdir(build_image_dir)
for filename in filelist:
if filename.startswith('sphx_glr') and filename.endswith('png'):
os.remove(os.path.join(build_image_dir, filename))
def parse_config(app):
"""Process the Sphinx Gallery configuration"""
try:
plot_gallery = eval(app.builder.config.plot_gallery)
except TypeError:
plot_gallery = bool(app.builder.config.plot_gallery)
gallery_conf = copy.deepcopy(DEFAULT_GALLERY_CONF)
gallery_conf.update(app.config.sphinx_gallery_conf)
gallery_conf.update(plot_gallery=plot_gallery)
gallery_conf.update(
abort_on_example_error=app.builder.config.abort_on_example_error)
gallery_conf['src_dir'] = app.builder.srcdir
backreferences_warning = """\n========
Sphinx-Gallery now requires you to set the configuration variable
'backreferences_dir' in your config to activate the
backreferences. That is mini galleries clustered by the functions used
in the example scripts. Have a look at it in sphinx-gallery
https://sphinx-gallery.readthedocs.io/en/stable/index.html#examples-using-numpy-linspace
"""
if gallery_conf.get("mod_example_dir", False):
update_msg = """\nFor a quick fix try replacing 'mod_example_dir'
by 'backreferences_dir' in your conf.py file. If that does not solve the
present issue read carefully how to update in the online documentation
https://sphinx-gallery.readthedocs.io/en/latest/advanced_configuration.html#references-to-examples"""
gallery_conf['backreferences_dir'] = gallery_conf['mod_example_dir']
logger.warning(
"Old configuration for backreferences detected \n"
"using the configuration variable `mod_example_dir`\n"
"%s%s",
backreferences_warning,
update_msg,
type=DeprecationWarning)
elif gallery_conf['backreferences_dir'] is None:
no_care_msg = """
If you don't care about this features set in your conf.py
'backreferences_dir': False\n"""
logger.warning(backreferences_warning + no_care_msg)
gallery_conf['backreferences_dir'] = os.path.join(
'modules', 'generated')
logger.warning(
"Using old default 'backreferences_dir':'%s'.\n"
"This will be disabled in future releases\n",
gallery_conf['backreferences_dir'],
type=DeprecationWarning)
# this assures I can call the config in other places
app.config.sphinx_gallery_conf = gallery_conf
app.config.html_static_path.append(glr_path_static())
return gallery_conf
def get_subsections(srcdir, examples_dir, sortkey):
"""Returns the list of subsections of a gallery
Parameters
----------
srcdir : str
absolute path to directory containing conf.py
examples_dir : str
path to the examples directory relative to conf.py
sortkey : sortkey
Returns
-------
out : list
sorted list of gallery subsection folder names
"""
subfolders = [subfolder for subfolder in os.listdir(examples_dir)
if os.path.exists(os.path.join(examples_dir, subfolder, 'README.txt'))]
base_examples_dir_path = os.path.relpath(examples_dir, srcdir)
subfolders_with_path = [os.path.join(base_examples_dir_path, item)
for item in subfolders]
sorted_subfolders = sorted(subfolders_with_path, key=sortkey)
return [subfolders[i] for i in [subfolders_with_path.index(item)
for item in sorted_subfolders]]
def _prepare_sphx_glr_dirs(gallery_conf, srcdir):
"""Creates necessary folders for sphinx_gallery files """
examples_dirs = gallery_conf['examples_dirs']
gallery_dirs = gallery_conf['gallery_dirs']
if not isinstance(examples_dirs, list):
examples_dirs = [examples_dirs]
if not isinstance(gallery_dirs, list):
gallery_dirs = [gallery_dirs]
for outdir in gallery_dirs:
if not os.path.exists(outdir):
os.makedirs(outdir)
if bool(gallery_conf['backreferences_dir']):
backreferences_dir = os.path.join(
srcdir, gallery_conf['backreferences_dir'])
if not os.path.exists(backreferences_dir):
os.makedirs(backreferences_dir)
return zip(examples_dirs, gallery_dirs)
def generate_gallery_rst(app):
"""Generate the Main examples gallery reStructuredText
Start the sphinx-gallery configuration and recursively scan the examples
directories in order to populate the examples gallery
"""
logger.info('Generating gallery...', color='white')
gallery_conf = parse_config(app)
clean_gallery_out(app.builder.outdir)
seen_backrefs = set()
computation_times = []
workdirs = _prepare_sphx_glr_dirs(gallery_conf,
app.builder.srcdir)
for examples_dir, gallery_dir in workdirs:
examples_dir = os.path.join(app.builder.srcdir, examples_dir)
gallery_dir = os.path.join(app.builder.srcdir, gallery_dir)
if not os.path.exists(os.path.join(examples_dir, 'README.txt')):
raise FileNotFoundError("Main example directory {0} does not "
"have a README.txt file. Please write "
"one to introduce your gallery."
.format(examples_dir))
# Here we don't use an os.walk, but we recurse only twice: flat is
# better than nested.
this_fhindex, this_computation_times = generate_dir_rst(
examples_dir, gallery_dir, gallery_conf, seen_backrefs)
computation_times += this_computation_times
# we create an index.rst with all examples
with codecs.open(os.path.join(gallery_dir, 'index.rst'), 'w',
encoding='utf-8') as fhindex:
# :orphan: to suppress "not included in TOCTREE" sphinx warnings
fhindex.write(":orphan:\n\n" + this_fhindex)
for subsection in get_subsections(app.builder.srcdir, examples_dir, gallery_conf['subsection_order']):
src_dir = os.path.join(examples_dir, subsection)
target_dir = os.path.join(gallery_dir, subsection)
this_fhindex, this_computation_times = generate_dir_rst(src_dir, target_dir, gallery_conf,
seen_backrefs)
fhindex.write(this_fhindex)
computation_times += this_computation_times
if gallery_conf['download_all_examples']:
download_fhindex = generate_zipfiles(gallery_dir)
fhindex.write(download_fhindex)
fhindex.write(SPHX_GLR_SIG)
if gallery_conf['plot_gallery']:
logger.info("Computation time summary:", color='white')
for time_elapsed, fname in sorted(computation_times)[::-1]:
if time_elapsed is not None:
logger.info("\t- %s : %.2g sec", fname, time_elapsed)
else:
logger.info("\t- %s : not run", fname)
def touch_empty_backreferences(app, what, name, obj, options, lines):
"""Generate empty back-reference example files
This avoids inclusion errors/warnings if there are no gallery
examples for a class / module that is being parsed by autodoc"""
if not bool(app.config.sphinx_gallery_conf['backreferences_dir']):
return
examples_path = os.path.join(app.srcdir,
app.config.sphinx_gallery_conf[
"backreferences_dir"],
"%s.examples" % name)
if not os.path.exists(examples_path):
# touch file
open(examples_path, 'w').close()
def sumarize_failing_examples(app, exception):
"""Collects the list of falling examples during build and prints them with the traceback
Raises ValueError if there where failing examples
"""
if exception is not None:
return
# Under no-plot Examples are not run so nothing to summarize
if not app.config.sphinx_gallery_conf['plot_gallery']:
return
gallery_conf = app.config.sphinx_gallery_conf
failing_examples = set(gallery_conf['failing_examples'].keys())
expected_failing_examples = set([os.path.normpath(os.path.join(app.srcdir, path))
for path in
gallery_conf['expected_failing_examples']])
examples_expected_to_fail = failing_examples.intersection(
expected_failing_examples)
if examples_expected_to_fail:
logger.info("Examples failing as expected:", color='brown')
for fail_example in examples_expected_to_fail:
logger.info('%s failed leaving traceback:', fail_example)
logger.info(gallery_conf['failing_examples'][fail_example])
examples_not_expected_to_fail = failing_examples.difference(
expected_failing_examples)
fail_msgs = []
if examples_not_expected_to_fail:
fail_msgs.append("Unexpected failing examples:")
for fail_example in examples_not_expected_to_fail:
fail_msgs.append(fail_example + ' failed leaving traceback:\n' +
gallery_conf['failing_examples'][fail_example] + '\n')
examples_not_expected_to_pass = expected_failing_examples.difference(
failing_examples)
if examples_not_expected_to_pass:
fail_msgs.append("Examples expected to fail, but not failling:\n" +
"Please remove these examples from\n" +
"sphinx_gallery_conf['expected_failing_examples']\n" +
"in your conf.py file"
"\n".join(examples_not_expected_to_pass))
if fail_msgs:
raise ValueError("Here is a summary of the problems encountered when "
"running the examples\n\n" + "\n".join(fail_msgs) +
"\n" + "-" * 79)
def get_default_config_value(key):
def default_getter(conf):
return conf['sphinx_gallery_conf'].get(key, DEFAULT_GALLERY_CONF[key])
return default_getter
def setup(app):
"""Setup sphinx-gallery sphinx extension"""
sphinx_compatibility._app = app
app.add_config_value('sphinx_gallery_conf', DEFAULT_GALLERY_CONF, 'html')
for key in ['plot_gallery', 'abort_on_example_error']:
app.add_config_value(key, get_default_config_value(key), 'html')
app.add_stylesheet('gallery.css')
# Sphinx < 1.6 calls it `_extensions`, >= 1.6 is `extensions`.
extensions_attr = '_extensions' if hasattr(
app, '_extensions') else 'extensions'
if 'sphinx.ext.autodoc' in getattr(app, extensions_attr):
app.connect('autodoc-process-docstring', touch_empty_backreferences)
app.connect('builder-inited', generate_gallery_rst)
app.connect('build-finished', sumarize_failing_examples)
app.connect('build-finished', embed_code_links)
metadata = {'parallel_read_safe': True}
return metadata
def setup_module():
# HACK: Stop nosetests running setup() above
pass