diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 54fc9d2a5..d6f888eff 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -67,7 +67,7 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} if: matrix.distrib != 'mamba' # Python (if conda) - - uses: mamba-org/setup-micromamba@v1 + - uses: mamba-org/setup-micromamba@v2 with: environment-name: test init-shell: bash diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f97cc15dc..2bda40d21 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.6 + rev: v0.6.9 hooks: - id: ruff-format exclude: plot_syntaxerror @@ -30,16 +30,16 @@ repos: args: [--strict, -c, .yamllint.yml] - repo: https://github.com/sphinx-contrib/sphinx-lint - rev: v0.9.1 + rev: v1.0.0 hooks: - id: sphinx-lint - repo: https://github.com/tox-dev/pyproject-fmt - rev: 2.2.1 + rev: 2.2.4 hooks: - id: pyproject-fmt additional_dependencies: [tox] - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.18 + rev: v0.20.2 hooks: - id: validate-pyproject diff --git a/CHANGES.rst b/CHANGES.rst index 632114566..7ca535441 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,31 @@ Changelog ========= +v0.18.0 +------- + +**Implemented enhancements:** + +- Allow to disable writing computation times `#1385 `__ (`bmwiedemann `__) +- [ENH] Add option to render multiple images from same cell as single-img `#1384 `__ (`tsbinns `__) + +**Fixed bugs:** + +- Fix ``indexst`` variable does not exist when own index gallery is first `#1383 `__ (`lucyleeow `__) + +**Project maintenance** + +- [pre-commit.ci] pre-commit autoupdate `#1387 `__ (`pre-commit-ci[bot] `__) +- Bump mamba-org/setup-micromamba from 1 to 2 in the actions group `#1386 `__ (`dependabot[bot] `__) +- [pre-commit.ci] pre-commit autoupdate `#1380 `__ (`pre-commit-ci[bot] `__) +- [pre-commit.ci] pre-commit autoupdate `#1379 `__ (`pre-commit-ci[bot] `__) +- [pre-commit.ci] pre-commit autoupdate `#1378 `__ (`pre-commit-ci[bot] `__) +- [pre-commit.ci] pre-commit autoupdate `#1377 `__ (`pre-commit-ci[bot] `__) +- [pre-commit.ci] pre-commit autoupdate `#1376 `__ (`pre-commit-ci[bot] `__) +- [pre-commit.ci] pre-commit autoupdate `#1375 `__ (`pre-commit-ci[bot] `__) +- [pre-commit.ci] pre-commit autoupdate `#1373 `__ (`pre-commit-ci[bot] `__) +- [pre-commit.ci] pre-commit autoupdate `#1372 `__ (`pre-commit-ci[bot] `__) + v0.17.1 ------- diff --git a/doc/configuration.rst b/doc/configuration.rst index 8d3762d36..01989e5e9 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -65,6 +65,7 @@ file, inside a ``sphinx_gallery_conf`` dictionary. **Compute costs** - ``min_reported_time`` (:ref:`min_reported_time`) +- ``write_computation_times`` (:ref:`write_computation_times`) - ``show_memory`` (:ref:`show_memory`) - ``junit`` (:ref:`junit_xml`) @@ -102,10 +103,12 @@ Some options can also be set or overridden on a file-by-file basis: - ``# sphinx_gallery_failing_thumbnail`` (:ref:`failing_thumbnail`) - ``# sphinx_gallery_dummy_images`` (:ref:`dummy_images`) - ``# sphinx_gallery_capture_repr`` (:ref:`capture_repr`) +- ``# sphinx_gallery_multi_image`` (:ref:`multi_image`) Some options can be set on a per-code-block basis in a file: - ``# sphinx_gallery_defer_figures`` (:ref:`defer_figures`) +- ``# sphinx_gallery_multi_image_block`` (:ref:`multi_image`) Some options can be set on a per-line basis in a file: - ``# sphinx_gallery_start_ignore`` and ``# sphinx_gallery_end_ignore`` (:ref:`hiding_code_blocks`) @@ -1913,6 +1916,44 @@ further deferred, if desired). The following produces only one plot:: plt.plot([2, 2]) plt.show() +.. _multi_image: + +Controlling the layout of multiple figures from the same code block +=================================================================== + +By default, multiple figures generated from the same code block are stacked +side-by-side. Particularly for wide figures, this can lead to cases where images are +highly shrunk, losing their legibility. This behaviour can be controlled using two +optional variables: + +- a file-wide ``sphinx_gallery_multi_image`` variable +- a code block-specific ``sphinx_gallery_multi_image_block`` variable + +The default behaviour is to treat these variables as being set to ``"multi"``, which +causes figures to be stacked side-by-side. Setting these variables to ``"single"`` will +allow figures produced from a code block to be displayed as a single column. + +For instance, adding:: + + # sphinx_gallery_multi_image = "single" + +somewhere in an example file will cause images from all code blocks where multiple +figures are produced to be displayed in a single column. + +Alternatively, adding:: + + # sphinx_gallery_multi_image_block = "single" + +to a code block will cause multiple figures from only that code block to be displayed in +a single column. + +Conversely, if ``sphinx_gallery_multi_image = "single"`` is set for the whole file, +adding ``sphinx_gallery_multi_image_block = "multi"`` can restore the default behaviour +for a single code block. + +See the example :ref:`sphx_glr_auto_examples_plot_9_multi_image_separate.py` for a +demonstration of this functionality. + .. _hiding_code_blocks: Hiding lines of code @@ -2293,6 +2334,14 @@ The ``min_reported_time`` parameter can be set to a number of seconds. The duration of scripts that ran faster than that amount will not be logged nor embedded in the html output. +.. _write_computation_times: + +Write computation times +======================= + +Set to ``False`` if you want to omit computation times from all gallery outputs. +This helps with reproducible builds. +Default is ``True`` unless the ``SOURCE_DATE_EPOCH`` environment variable is set. .. _show_memory: @@ -2537,13 +2586,16 @@ Manually passing ``index.rst`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can bypass Sphinx-Gallery automatically creating an ``index.rst`` from a -``GALLERY_HEADER.rst`` in a gallery directory or subdirectory. If your +``GALLERY_HEADER.rst`` in a gallery directory or nested sub-gallery directory. If your ``copyfile_regex`` includes ``index.rst``, and you have an ``index.rst`` in the -gallery-source instead of the 'GALLERY_HEADER' file, Sphinx-Gallery will use that instead of -the index it automatically makes. If you do this, you are responsible for +gallery-source (i.e., a :ref:`examples_dirs ` directory), +Sphinx-Gallery will use that instead and not make an index file for that gallery +or any of its sub-galleries. +If you pass your own ``index.rst`` file, you are responsible for adding your own Sphinx ``toctree`` in that index (or elsewhere in your Sphinx documentation) that includes any gallery items or other files in that -directory. +directory. You are also responsible for adding any necessary ``index.rst`` +files for that gallery's sub-galleries. .. _show_api_usage: diff --git a/examples/plot_1_exp.py b/examples/plot_1_exp.py index ec4fab65b..5d98f56ac 100644 --- a/examples/plot_1_exp.py +++ b/examples/plot_1_exp.py @@ -2,11 +2,12 @@ Plotting the exponential function ================================= -This example demonstrates how to import a local module and how images are -stacked when two plots are created in one code block. The variable ``N`` from -the example 'Local module' (file ``local_module.py``) is imported in the code -below. Further, note that when there is only one code block in an example, the -output appears before the code block. +This example demonstrates how to import a local module and how images are stacked when +two plots are created in one code block (see the :doc:`plot_9_multi_image_separate` +example for information on controlling this behaviour). The variable ``N`` from the +example 'Local module' (file ``local_module.py``) is imported in the code below. +Further, note that when there is only one code block in an example, the output appears +before the code block. """ # Code source: Óscar Nájera diff --git a/examples/plot_9_multi_image_separate.py b/examples/plot_9_multi_image_separate.py new file mode 100644 index 000000000..d6071974c --- /dev/null +++ b/examples/plot_9_multi_image_separate.py @@ -0,0 +1,53 @@ +""" +Force plots to be displayed on separate lines +============================================= +This example demonstrates how the visualisation of multiple plots produced from a single +code block can be controlled. The default behaviour is to stack plots side-by-side, +however this can be overridden to display each plot created by the code block on a +separate line, preserving their size. + +There are two config options to control this behaviour: + +- a file-wide ``sphinx_gallery_multi_image`` variable +- a code block-specific ``sphinx_gallery_multi_image_block`` variable + +Setting these variables to ``"single"`` will force plots to be displayed on separate +lines. Default behaviour is to treat these variables as being set to ``"multi"``. + +Below we demonstrate how the file-wide ``sphinx_gallery_multi_image`` variable can be +used to display plots on separate lines. +""" + +# Code source: Thomas S. Binns +# License: BSD 3 clause + +# sphinx_gallery_multi_image = "single" + +import matplotlib.pyplot as plt +import numpy as np + +# %% + +# Plots will be shown on separate lines + +fig, ax = plt.subplots(1, 1, figsize=(8, 4)) +ax.pcolormesh(np.random.randn(100, 100)) + +fig, ax = plt.subplots(1, 1, figsize=(8, 4)) +ax.pcolormesh(np.random.randn(100, 100)) + +######################################################################################## +# Now, we show how the ``sphinx_gallery_multi_image_block`` variable can be used to +# control the behaviour for a specific code block, here reverting to the default +# behaviour of stacking plots side-by-side. + +# %% + +# sphinx_gallery_multi_image_block = "multi" +# ↑↑↑ Return to default behaviour for just this cell + +fig, ax = plt.subplots(1, 1, figsize=(8, 4)) +ax.pcolormesh(np.random.randn(100, 100)) + +fig, ax = plt.subplots(1, 1, figsize=(8, 4)) +ax.pcolormesh(np.random.randn(100, 100)) diff --git a/sphinx_gallery/__init__.py b/sphinx_gallery/__init__.py index 06e8a7650..fee348894 100644 --- a/sphinx_gallery/__init__.py +++ b/sphinx_gallery/__init__.py @@ -8,7 +8,7 @@ # dev versions should have "dev" in them, stable should not. # doc/conf.py makes use of this to set the version drop-down. -__version__ = "0.17.1" +__version__ = "0.18.0" def glr_path_static(): diff --git a/sphinx_gallery/gen_gallery.py b/sphinx_gallery/gen_gallery.py index c3b29f4a9..95684473e 100644 --- a/sphinx_gallery/gen_gallery.py +++ b/sphinx_gallery/gen_gallery.py @@ -111,6 +111,7 @@ def __call__(self, gallery_conf, script_vars): "expected_failing_examples": set(), "thumbnail_size": (400, 280), # Default CSS does 0.4 scaling (160, 112) "min_reported_time": 0, + "write_computation_times": os.getenv("SOURCE_DATE_EPOCH") is None, "binder": {}, "jupyterlite": {}, "promote_jupyter_magic": False, @@ -504,7 +505,7 @@ def _fill_gallery_conf_defaults(sphinx_gallery_conf, app=None, check_keys=True): return gallery_conf -def get_subsections(srcdir, examples_dir, gallery_conf, check_for_index=True): +def get_subsections(srcdir, examples_dir, gallery_conf, check_for_header=True): """Return the list of subsections of a gallery. Parameters @@ -515,8 +516,8 @@ def get_subsections(srcdir, examples_dir, gallery_conf, check_for_index=True): path to the examples directory relative to conf.py gallery_conf : Dict[str, Any] Sphinx-Gallery configuration dictionary. - check_for_index : bool - only return subfolders contain a GALLERY_HEADER file, default True + check_for_header : bool + only return subfolders that contain a GALLERY_HEADER file, default True Returns ------- @@ -530,22 +531,27 @@ def get_subsections(srcdir, examples_dir, gallery_conf, check_for_index=True): if isinstance(sortkey, list): sortkey = ExplicitOrder(sortkey) subfolders = [subfolder for subfolder in os.listdir(examples_dir)] - if check_for_index: + if check_for_header: subfolders = [ subfolder for subfolder in subfolders + # Return is not `None` only when a gallery head file is found if _get_gallery_header( os.path.join(examples_dir, subfolder), gallery_conf, raise_error=False ) is not None ] else: - # just make sure its a directory + # just make sure its a directory, that is not `__pycache__` subfolders = [ subfolder for subfolder in subfolders - if os.path.isdir(os.path.join(examples_dir, subfolder)) + if ( + subfolder != "__pycache__" + and os.path.isdir(os.path.join(examples_dir, subfolder)) + ) ] + 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 @@ -748,11 +754,13 @@ def generate_gallery_rst(app): is_subsection=False, ) - # `this_context` is None when user provides own index.rst + # `this_content` is None when user provides own index.rst sg_root_index = this_content is not None costs += this_costs write_computation_times(gallery_conf, gallery_dir_abs_path, this_costs) + # `indexst` variable must exist, as passed to `_finish_index_rst` + indexst = "" # Create root gallery index.rst if sg_root_index: # :orphan: to suppress "not included in TOCTREE" sphinx warnings @@ -768,7 +776,7 @@ def generate_gallery_rst(app): app.builder.srcdir, examples_dir_abs_path, gallery_conf, - check_for_index=sg_root_index, + check_for_header=sg_root_index, ) for subsection in subsecs: src_dir = os.path.join(examples_dir_abs_path, subsection) @@ -927,6 +935,8 @@ def write_computation_times(gallery_conf, target_dir, costs): costs: List[Dict] List of dicts of computation costs and paths, see gen_rst.py for details. """ + if not gallery_conf["write_computation_times"]: + return total_time = sum(cost["t"] for cost in costs) if target_dir is None: # all galleries together out_dir = gallery_conf["src_dir"] diff --git a/sphinx_gallery/gen_rst.py b/sphinx_gallery/gen_rst.py index 74f691d71..2faa858c5 100644 --- a/sphinx_gallery/gen_rst.py +++ b/sphinx_gallery/gen_rst.py @@ -1000,12 +1000,21 @@ def execute_code_block( sys.path.append(os.getcwd()) # Save figures unless there is a `sphinx_gallery_defer_figures` flag - match = re.search( + defer_figs_match = re.search( r"^[\ \t]*#\s*sphinx_gallery_defer_figures[\ \t]*\n?", block.content, re.MULTILINE, ) - need_save_figures = match is None + need_save_figures = defer_figs_match is None + + # Add `sphinx_gallery_multi_image_block` setting to block variables + # (extract config rather than just regex search since the option's value is needed) + script_vars["multi_image"] = py_source_parser.extract_file_config( + block.content + ).get("multi_image_block") + + # Add file_conf to script_vars to be read by image scrapers + script_vars["file_conf"] = file_conf try: # The "compile" step itself can fail on a SyntaxError, so just prepend diff --git a/sphinx_gallery/scrapers.py b/sphinx_gallery/scrapers.py index b465ef899..bcb12ffc7 100644 --- a/sphinx_gallery/scrapers.py +++ b/sphinx_gallery/scrapers.py @@ -204,15 +204,25 @@ def matplotlib_scraper(block, block_vars, gallery_conf, **kwargs): ) ) + # Determine whether single-img should be converted to multi-img + convert_to_multi_image = True # default is to convert + if block_vars.get("multi_image") is not None: # block setting takes precedence + convert_to_multi_image = block_vars["multi_image"] != "single" + elif block_vars.get("file_conf") is not None: # then file setting + convert_to_multi_image = block_vars["file_conf"].get("multi_image") != "single" + plt.close("all") rst = "" if len(image_rsts) == 1: rst = image_rsts[0] elif len(image_rsts) > 1: - image_rsts = [ - re.sub(r":class: sphx-glr-single-img", ":class: sphx-glr-multi-img", image) - for image in image_rsts - ] + if convert_to_multi_image: + image_rsts = [ + re.sub( + r":class: sphx-glr-single-img", ":class: sphx-glr-multi-img", image + ) + for image in image_rsts + ] image_rsts = [ HLIST_IMAGE_MATPLOTLIB + indent(image, " " * 6) for image in image_rsts ] diff --git a/sphinx_gallery/tests/conftest.py b/sphinx_gallery/tests/conftest.py index 4087a4177..4f904d954 100644 --- a/sphinx_gallery/tests/conftest.py +++ b/sphinx_gallery/tests/conftest.py @@ -16,6 +16,20 @@ from sphinx_gallery.scrapers import _import_matplotlib from sphinx_gallery.utils import _get_image +INDEX_RST = """ +============= +Own index.rst +============= + +Own index.rst file. + +.. toctree:: + + plot_1 + plot_2 + plot_3 +""" + NESTED_PY = """\"\"\" Header ====== @@ -218,17 +232,20 @@ def sphinx_app_wrapper(tmpdir, conf_file, rst_file, req_mpl, req_pil): # Copy files to 'examples/' as well because default `examples_dirs` is # '../examples' - for tests where we don't update config shutil.copytree((_fixturedir / "src"), (Path(tmpdir) / "examples")) - if rst_file: - with open((srcdir / "minigallery_test.rst"), "w") as rstfile: - rstfile.write(rst_file) + if rst_file == "own index.rst": + with open((srcdir / "src" / "index.rst"), "w") as file: + file.write(INDEX_RST) + elif rst_file: + with open((srcdir / "minigallery_test.rst"), "w") as file: + file.write(rst_file) # Add nested gallery if "sub_folder/sub_sub_folder" in rst_file: dir_path = srcdir / "src" / "sub_folder" / "sub_sub_folder" dir_path.mkdir(parents=True) - with open((dir_path / "plot_nested.py"), "w") as pyfile: - pyfile.write(NESTED_PY) - with open((dir_path / "GALLERY_HEADER.rst"), "w") as rstfile: - rstfile.write(GALLERY_HEADER) + with open((dir_path / "plot_nested.py"), "w") as file: + file.write(NESTED_PY) + with open((dir_path / "GALLERY_HEADER.rst"), "w") as file: + file.write(GALLERY_HEADER) base_config = f""" import os diff --git a/sphinx_gallery/tests/test_full.py b/sphinx_gallery/tests/test_full.py index 8aa519638..63f16f2a8 100644 --- a/sphinx_gallery/tests/test_full.py +++ b/sphinx_gallery/tests/test_full.py @@ -41,7 +41,7 @@ # # total number of plot_*.py files in # (examples + examples_rst_index + examples_with_rst + examples_README_header) -N_EXAMPLES = 17 + 3 + 2 + 1 +N_EXAMPLES = 19 + 3 + 2 + 1 N_FAILING = 4 N_GOOD = N_EXAMPLES - N_FAILING # galleries that run w/o error # passthroughs and non-executed examples in @@ -409,6 +409,36 @@ def test_thumbnail_expected_failing_examples(sphinx_app, tmpdir): assert corr < 0.7 # i.e. thumbnail and "BROKEN" stamp are not identical +def test_multi_image(sphinx_app): + """Test `sphinx_gallery_multi_image(_block)` variables.""" + generated_examples_dir = op.join(sphinx_app.outdir, "auto_examples") + + # Check file-wide `sphinx_gallery_multi_image="single"` produces no multi-img + html_fname = op.join(generated_examples_dir, "plot_multi_image_separate.html") + with codecs.open(html_fname, "r", "utf-8") as fid: + html = fid.read() + assert "sphx-glr-single-img" in html + assert "sphx-glr-multi-img" not in html + + # Check block-specific `sphinx_gallery_multi_image_block` produces mixed img classes + html_fname = op.join(generated_examples_dir, "plot_multi_image_block_separate.html") + with codecs.open(html_fname, "r", "utf-8") as fid: + html = fid.read() + # find start of each code block + matches = re.finditer('
', html) + starts = [match.start() for match in matches] + [-1] + assert len(starts) == 4 # 3 code block plus an extra for end index + for block_idx, (start, end) in enumerate(zip(starts[:-1], starts[1:])): + # ignore first code block (just imports) + block_html = html[start:end] + if block_idx == 1: # multi-img classes for this code block + assert "sphx-glr-multi-img" in block_html + assert "sphx-glr-single-img" not in block_html + elif block_idx == 2: # single-img classes for this code block + assert "sphx-glr-single-img" in block_html + assert "sphx-glr-multi-img" not in block_html + + def test_command_line_args_img(sphinx_app): generated_examples_dir = op.join(sphinx_app.outdir, "auto_examples") thumb_fname = "../_images/sphx_glr_plot_command_line_args_thumb.png" @@ -889,7 +919,7 @@ def test_rebuild(tmpdir_factory, sphinx_app): else: assert ( re.match( - ".*[0|1] added, ([1-9]|10) changed, 0 removed$.*", + ".*[0|1] added, ([1-9]|1[0-1]) changed, 0 removed$.*", status, re.MULTILINE | re.DOTALL, ) @@ -1542,9 +1572,9 @@ def test_recommend_n_examples(sphinx_app): assert '

Related examples

' in html assert count == n_examples # Check the same 3 related examples are shown (can change when new examples added) - assert "sphx-glr-auto-examples-plot-repr-py" in html - assert "sphx-glr-auto-examples-plot-matplotlib-backend-py" in html - assert "sphx-glr-auto-examples-plot-second-future-imports-py" in html + assert "sphx-glr-auto-examples-plot-defer-figures-py" in html + assert "sphx-glr-auto-examples-plot-webp-py" in html + assert "sphx-glr-auto-examples-plot-command-line-args-py" in html def test_sidebar_components_download_links(sphinx_app): diff --git a/sphinx_gallery/tests/test_gen_gallery.py b/sphinx_gallery/tests/test_gen_gallery.py index ea2c41d82..a73f0bf35 100644 --- a/sphinx_gallery/tests/test_gen_gallery.py +++ b/sphinx_gallery/tests/test_gen_gallery.py @@ -2,12 +2,14 @@ # License: 3-clause BSD r"""Test Sphinx-Gallery gallery generation.""" +import inspect import json import os import re from pathlib import Path import pytest +from sphinx.application import Sphinx from sphinx.config import is_serializable from sphinx.errors import ConfigError, ExtensionError, SphinxWarning @@ -405,6 +407,21 @@ def test_collect_gallery_files_ignore_pattern(tmpdir, gallery_conf): assert collected_files == expected_files +@pytest.mark.add_conf( + content=r""" +sphinx_gallery_conf = { + 'examples_dirs': 'src', + 'gallery_dirs': 'ex', + 'copyfile_regex': r'.*\.rst', +}""" +) +@pytest.mark.add_rst(file="own index.rst") +def test_own_index_first(sphinx_app_wrapper): + """Test `generate_gallery_rst` works when own index gallery is first (and only).""" + # Issue #1382 + sphinx_app_wrapper.build_sphinx_app() + + @pytest.mark.add_conf( content=""" sphinx_gallery_conf = { @@ -518,7 +535,10 @@ def test_only_warn_on_example_error(sphinx_app_wrapper): ) def test_only_warn_on_example_error_sphinx_warning(sphinx_app_wrapper): """Test behaviour of only_warn_on_example_error flag.""" - sphinx_app_wrapper.kwargs["warningiserror"] = True + # https://github.com/sphinx-doc/sphinx/pull/12743/files + for key in ("warningiserror", "exception_on_warning"): + if key in inspect.getfullargspec(Sphinx).args: + sphinx_app_wrapper.kwargs[key] = True example_dir = Path(sphinx_app_wrapper.srcdir) / "src" with open(example_dir / "plot_3.py", "w", encoding="utf-8") as fid: fid.write(f"{MINIMAL_HEADER}raise ValueError") diff --git a/sphinx_gallery/tests/test_gen_rst.py b/sphinx_gallery/tests/test_gen_rst.py index ea94196a4..260dc2aa1 100644 --- a/sphinx_gallery/tests/test_gen_rst.py +++ b/sphinx_gallery/tests/test_gen_rst.py @@ -651,6 +651,7 @@ def test_gen_dir_rst(gallery_conf, gallery_header): ], ) def test_gen_dir_rst_invalid_header(gallery_conf, gallery_header): + """Check `_get_gallery_header` raises error for invalid header extension.""" with open(os.path.join(gallery_conf["src_dir"], gallery_header), "wb") as fid: fid.write("Testing\n=======\n\nÓscar here.".encode()) with pytest.raises(ExtensionError, match="does not have a GALLERY_HEADER"): diff --git a/sphinx_gallery/tests/test_scrapers.py b/sphinx_gallery/tests/test_scrapers.py index 955793a23..1c330066f 100644 --- a/sphinx_gallery/tests/test_scrapers.py +++ b/sphinx_gallery/tests/test_scrapers.py @@ -66,12 +66,15 @@ def test_save_matplotlib_figures(make_gallery_conf, ext): fname = gallery_conf["gallery_dir"] + fname assert os.path.isfile(fname) + def _create_two_images(): + image_path_iterator.next() + image_path_iterator.next() + plt.plot(1, 1) + plt.figure() + plt.plot(1, 1) + # Test capturing 2 images with shifted start number - image_path_iterator.next() - image_path_iterator.next() - plt.plot(1, 1) - plt.figure() - plt.plot(1, 1) + _create_two_images() image_rst = save_figures(block, block_vars, gallery_conf) assert len(image_path_iterator) == 5 for ii in range(4, 6): @@ -80,6 +83,33 @@ def test_save_matplotlib_figures(make_gallery_conf, ext): fname = gallery_conf["gallery_dir"] + fname assert os.path.isfile(fname) + # Test `sphinx_gallery_multi_image(_block)` variables work; these variables prevent + # images with `sphx-glr-single-img` classes from being converted to + # `sphx-glr-multi-img` classes; requires > 1 image + + # Test file-wide `sphinx_gallery_multi_image` variable + _create_two_images() + block_vars["file_conf"] = {"multi_image": "single"} + image_rst = save_figures(block, block_vars, gallery_conf) + assert "sphx-glr-single-img" in image_rst + assert "sphx-glr-multi-img" not in image_rst + + # Test block-specific `sphinx_gallery_multi_image_block` variable + # (test with default `sphinx_gallery_multi_image`, i.e. != "single") + _create_two_images() + block_vars["file_conf"] = {} + block_vars["multi_image"] = "single" + image_rst = save_figures(block, block_vars, gallery_conf) + assert "sphx-glr-single-img" in image_rst + assert "sphx-glr-multi-img" not in image_rst + # (test block-specific setting overrides file-wide setting) + _create_two_images() + block_vars["file_conf"] = {"multi_image": "single"} + block_vars["multi_image"] = "multi" + image_rst = save_figures(block, block_vars, gallery_conf) + assert "sphx-glr-single-img" not in image_rst + assert "sphx-glr-multi-img" in image_rst + def test_image_srcset_config(make_gallery_conf): with pytest.raises(ConfigError, match="'image_srcset' config allowed"): diff --git a/sphinx_gallery/tests/tinybuild/examples/plot_multi_image_block_separate.py b/sphinx_gallery/tests/tinybuild/examples/plot_multi_image_block_separate.py new file mode 100644 index 000000000..9e2389659 --- /dev/null +++ b/sphinx_gallery/tests/tinybuild/examples/plot_multi_image_block_separate.py @@ -0,0 +1,31 @@ +""" +Force images to be displayed on separate lines per block +======================================================== +This example demonstrates how the ``sphinx_gallery_multi_image_block`` option can be +used to force images to be displayed on separate lines for a specific block, rather than +the default behaviour of displaying them side-by-side. +""" + +import matplotlib.pyplot as plt +import numpy as np + +# %% + +# Default behaviour +fig, ax = plt.subplots(1, 1, figsize=(8, 4)) +ax.pcolormesh(np.random.randn(100, 100)) + +fig, ax = plt.subplots(1, 1, figsize=(8, 4)) +ax.pcolormesh(np.random.randn(100, 100)) + + +# %% + +# sphinx_gallery_multi_image_block = "single" + +# Non-default behaviour for just this cell +fig, ax = plt.subplots(1, 1, figsize=(8, 4)) +ax.pcolormesh(np.random.randn(100, 100)) + +fig, ax = plt.subplots(1, 1, figsize=(8, 4)) +ax.pcolormesh(np.random.randn(100, 100)) diff --git a/sphinx_gallery/tests/tinybuild/examples/plot_multi_image_separate.py b/sphinx_gallery/tests/tinybuild/examples/plot_multi_image_separate.py new file mode 100644 index 000000000..c86495d3b --- /dev/null +++ b/sphinx_gallery/tests/tinybuild/examples/plot_multi_image_separate.py @@ -0,0 +1,20 @@ +""" +Force images to be displayed on separate lines +============================================== +This example demonstrates how the ``sphinx_gallery_multi_image`` option can be used to +force images to be displayed on separate lines, rather than the default behaviour of +displaying them side-by-side. +""" + +# sphinx_gallery_multi_image = "single" + +# %% + +import matplotlib.pyplot as plt +import numpy as np + +fig, ax = plt.subplots(1, 1, figsize=(8, 4)) +ax.pcolormesh(np.random.randn(100, 100)) + +fig, ax = plt.subplots(1, 1, figsize=(8, 4)) +ax.pcolormesh(np.random.randn(100, 100))