diff --git a/.github/workflows/base.yml b/.github/workflows/base.yml index 0bdb2cb8..f44cf0af 100644 --- a/.github/workflows/base.yml +++ b/.github/workflows/base.yml @@ -69,6 +69,11 @@ jobs: python-version: 3.9 architecture: x64 + - name: Setup headless display + uses: pyvista/setup-headless-display-action@v2 + with: + qt: true + - name: Install noxfile requirements run: pip install -r noxfile-requirements.txt diff --git a/docs/examples/plot_0_sin.py b/docs/examples/plot_00_sin.py similarity index 100% rename from docs/examples/plot_0_sin.py rename to docs/examples/plot_00_sin.py diff --git a/docs/examples/plot_1_exp.py b/docs/examples/plot_01_exp.py similarity index 100% rename from docs/examples/plot_1_exp.py rename to docs/examples/plot_01_exp.py diff --git a/docs/examples/plot_2_seaborn.py b/docs/examples/plot_02_seaborn.py similarity index 100% rename from docs/examples/plot_2_seaborn.py rename to docs/examples/plot_02_seaborn.py diff --git a/docs/examples/plot_3_capture_repr.py b/docs/examples/plot_03_capture_repr.py similarity index 100% rename from docs/examples/plot_3_capture_repr.py rename to docs/examples/plot_03_capture_repr.py diff --git a/docs/examples/plot_4_choose_thumbnail.py b/docs/examples/plot_04_choose_thumbnail.py similarity index 100% rename from docs/examples/plot_4_choose_thumbnail.py rename to docs/examples/plot_04_choose_thumbnail.py diff --git a/docs/examples/plot_4b_provide_thumbnail.py b/docs/examples/plot_04b_provide_thumbnail.py similarity index 100% rename from docs/examples/plot_4b_provide_thumbnail.py rename to docs/examples/plot_04b_provide_thumbnail.py diff --git a/docs/examples/plot_5_unicode_everywhere.py b/docs/examples/plot_05_unicode_everywhere.py similarity index 100% rename from docs/examples/plot_5_unicode_everywhere.py rename to docs/examples/plot_05_unicode_everywhere.py diff --git a/docs/examples/plot_6_function_identifier.py b/docs/examples/plot_06_function_identifier.py similarity index 100% rename from docs/examples/plot_6_function_identifier.py rename to docs/examples/plot_06_function_identifier.py diff --git a/docs/examples/plot_7_sys_argv.py b/docs/examples/plot_07_sys_argv.py similarity index 100% rename from docs/examples/plot_7_sys_argv.py rename to docs/examples/plot_07_sys_argv.py diff --git a/docs/examples/plot_8_animations.py b/docs/examples/plot_08_animations.py similarity index 100% rename from docs/examples/plot_8_animations.py rename to docs/examples/plot_08_animations.py diff --git a/docs/examples/plot_9_plotly.py b/docs/examples/plot_09_plotly.py similarity index 100% rename from docs/examples/plot_9_plotly.py rename to docs/examples/plot_09_plotly.py diff --git a/docs/examples/plot_10_mayavi.py b/docs/examples/plot_10_mayavi.py new file mode 100644 index 00000000..768bb4cd --- /dev/null +++ b/docs/examples/plot_10_mayavi.py @@ -0,0 +1,24 @@ +""" +Example with the mayavi graphing library +======================================== + +Mkdocs-Gallery supports examples made with the +[mayavi library](http://docs.enthought.com/mayavi/mayavi/). + +This mayavi demo is from the +[mayavi documentation](https://docs.enthought.com/mayavi/mayavi/mlab.html#a-demo). +""" + +# Create the data. +from numpy import pi, sin, cos, mgrid +dphi, dtheta = pi/250.0, pi/250.0 +[phi,theta] = mgrid[0:pi+dphi*1.5:dphi,0:2*pi+dtheta*1.5:dtheta] +m0 = 4; m1 = 3; m2 = 2; m3 = 3; m4 = 6; m5 = 2; m6 = 6; m7 = 4; +r = sin(m0*phi)**m1 + cos(m2*phi)**m3 + sin(m4*theta)**m5 + cos(m6*theta)**m7 +x = r*sin(phi)*cos(theta) +y = r*cos(phi) +z = r*sin(phi)*sin(theta) + +# View it. +from mayavi import mlab +s = mlab.mesh(x, y, z) diff --git a/mkdocs.yml b/mkdocs.yml index 8d2926c9..6ed423fe 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -24,16 +24,16 @@ plugins: examples_dirs: - docs/examples # path to your example scripts, relative to mkdocs.yml - docs/tutorials - # TODO mayavi_examples gallery_dirs: - docs/generated/gallery # where to save gallery generated output. Note that you may or may not include them in - docs/generated/tutorials - # TODO tutorials and mayavi_examples backreferences_dir: docs/generated/backreferences # where to generate the back references summary doc_module: ['mkdocs_gallery', 'numpy'] # reference_url: {sphinx_gallery: None}, - image_scrapers: matplotlib + image_scrapers: + - matplotlib + - mayavi compress_images: ['images', 'thumbnails'] # specify the order of examples to be according to filename within_subsection_order: FileNameSortKey diff --git a/noxfile.py b/noxfile.py index 0b566a4d..c8f730c1 100644 --- a/noxfile.py +++ b/noxfile.py @@ -2,15 +2,17 @@ from itertools import product from json import dumps import logging +import os +import sys import nox # noqa +from packaging import version from pathlib import Path # noqa -import sys # add parent folder to python path so that we can import noxfile_utils.py # note that you need to "pip install -r noxfile-requiterements.txt" for this file to work. sys.path.append(str(Path(__file__).parent / "ci_tools")) -from nox_utils import PY27, PY37, PY36, PY35, PY38, PY39, PY310, PY311, power_session, rm_folder, rm_file, PowerSession # noqa +from nox_utils import PY37, PY38, PY39, PY310, PY311, power_session, rm_folder, rm_file, PowerSession # noqa pkg_name = "mkdocs_gallery" @@ -26,7 +28,6 @@ PY38: {"coverage": True, "pkg_specs": {"pip": ">19"}}, } - # set the default activated sessions, minimal for CI nox.options.sessions = ["tests", "flake8", "docs"] # , "docs", "gh_pages" nox.options.error_on_missing_interpreters = True @@ -87,9 +88,23 @@ def tests(session: PowerSession, coverage, pkg_specs): # install all requirements session.install_reqs(setup=True, install=True, tests=True, versions_dct=pkg_specs) - # Since our tests are currently limited, use our own doc generation as a test - session.install_reqs(phase="tests", phase_reqs=MKDOCS_GALLERY_EXAMPLES_REQS) + cannot_run_mayavi = version.parse(session.python) < version.parse(PY38) + if cannot_run_mayavi: + session.install_reqs(phase="tests", phase_reqs=MKDOCS_GALLERY_EXAMPLES_REQS) + else: + session.install_reqs(phase="tests", phase_reqs=MKDOCS_GALLERY_EXAMPLES_REQS+MKDOCS_GALLERY_EXAMPLES_MAYAVI_REQS) + + # Edit mkdocs config file + with open("mkdocs.yml", "r") as f: + mkdocs_config = f.readlines() + # Ignore failing mayavi example where mayavi is not installed + if cannot_run_mayavi: + with open("mkdocs-no-mayavi.yml", "w") as f: + for line in mkdocs_config: + if line == " expected_failing_examples:\n": + line = line + " - docs/examples/plot_10_mayavi.py\n" + f.write(line) # install CI-only dependencies # if install_ci_deps: @@ -114,9 +129,15 @@ def tests(session: PowerSession, coverage, pkg_specs): session.run2("python -m pytest --cache-clear -v tests/") # since our tests are too limited, we use our own mkdocs build as additional test for now. - session.run2("python -m mkdocs build") + if cannot_run_mayavi: + session.run2("python -m mkdocs build -f mkdocs-no-mayavi.yml") + else: + session.run2("python -m mkdocs build -f mkdocs.yml") # -- add a second build so that we can go through the caching/md5 side - session.run2("python -m mkdocs build") + if cannot_run_mayavi: + session.run2("python -m mkdocs build -f mkdocs-no-mayavi.yml") + else: + session.run2("python -m mkdocs build -f mkdocs.yml") else: # install self in "develop" mode so that coverage can be measured session.install2('-e', '.', '--no-deps') @@ -132,11 +153,19 @@ def tests(session: PowerSession, coverage, pkg_specs): "".format(pkg_name=pkg_name, test_xml=Folders.test_xml, test_html=Folders.test_html)) # -- use the doc generation for coverage - session.run2("coverage run --append --source src/{pkg_name} -m mkdocs build" - "".format(pkg_name=pkg_name, test_xml=Folders.test_xml, test_html=Folders.test_html)) + if cannot_run_mayavi: + session.run2("coverage run --append --source src/{pkg_name} -m mkdocs build -f mkdocs-no-mayavi.yml" + "".format(pkg_name=pkg_name, test_xml=Folders.test_xml, test_html=Folders.test_html)) + else: + session.run2("coverage run --append --source src/{pkg_name} -m mkdocs build -f mkdocs.yml" + "".format(pkg_name=pkg_name, test_xml=Folders.test_xml, test_html=Folders.test_html)) # -- add a second build so that we can go through the caching/md5 side - session.run2("coverage run --append --source src/{pkg_name} -m mkdocs build" - "".format(pkg_name=pkg_name, test_xml=Folders.test_xml, test_html=Folders.test_html)) + if cannot_run_mayavi: + session.run2("coverage run --append --source src/{pkg_name} -m mkdocs build -f mkdocs-no-mayavi.yml" + "".format(pkg_name=pkg_name, test_xml=Folders.test_xml, test_html=Folders.test_html)) + else: + session.run2("coverage run --append --source src/{pkg_name} -m mkdocs build -f mkdocs.yml" + "".format(pkg_name=pkg_name, test_xml=Folders.test_xml, test_html=Folders.test_html)) session.run2("coverage report") session.run2("coverage xml -o '{covxml}'".format(covxml=Folders.coverage_xml)) @@ -149,6 +178,9 @@ def tests(session: PowerSession, coverage, pkg_specs): # Use our own package to generate the badge session.run2("genbadge tests -i '%s' -o '%s' -t 100" % (Folders.test_xml, Folders.test_badge)) session.run2("genbadge coverage -i '%s' -o '%s'" % (Folders.coverage_xml, Folders.coverage_badge)) + # Cleanup + if os.path.exists("mkdocs-no-mayavi.yml"): + os.remove("mkdocs-no-mayavi.yml") @power_session(python=PY39, logsdir=Folders.runlogs) @@ -179,15 +211,18 @@ def flake8(session: PowerSession): "statsmodels", "plotly", # "memory_profiler", - "pillow" # PIL, required for image rescaling + "pillow", # PIL, required for image rescaling +] +MKDOCS_GALLERY_EXAMPLES_MAYAVI_REQS = [ + "PyQt5", # PyQt is required for the mayavi backend + "git+https://github.com/enthought/mayavi.git", # we want mayavi>=4.7.4 when available due to https://github.com/enthought/mayavi/pull/1272 ] @power_session(python=[PY39]) def docs(session: PowerSession): """Generates the doc and serves it on a local http server. Pass '-- build' to build statically instead.""" - - session.install_reqs(phase="docs", phase_reqs=["mkdocs"] + MKDOCS_GALLERY_EXAMPLES_REQS) + session.install_reqs(phase="docs", phase_reqs=["mkdocs"] + MKDOCS_GALLERY_EXAMPLES_REQS + MKDOCS_GALLERY_EXAMPLES_MAYAVI_REQS) # Install the plugin session.install2('.') @@ -196,30 +231,26 @@ def docs(session: PowerSession): # use posargs instead of "serve" session.run2("mkdocs %s" % " ".join(session.posargs)) else: - session.run2("mkdocs serve") + session.run2("mkdocs serve -f mkdocs.yml") @power_session(python=[PY39]) def publish(session: PowerSession): """Deploy the docs+reports on github pages. Note: this rebuilds the docs""" - - session.install_reqs( - phase="mkdocs", - phase_reqs=["mkdocs"] + MKDOCS_GALLERY_EXAMPLES_REQS - ) + session.install_reqs(phase="mkdocs", phase_reqs=["mkdocs"] + MKDOCS_GALLERY_EXAMPLES_REQS + MKDOCS_GALLERY_EXAMPLES_MAYAVI_REQS) # Install the plugin session.install2('.') # possibly rebuild the docs in a static way (mkdocs serve does not build locally) - session.run2("mkdocs build") + session.run2("mkdocs build -f mkdocs.yml") # check that the doc has been generated with coverage if not Folders.site_reports.exists(): raise ValueError("Test reports have not been built yet. Please run 'nox -s tests-3.7' first") # publish the docs - session.run2("mkdocs gh-deploy") + session.run2("mkdocs gh-deploy -f mkdocs.yml") # publish the coverage - now in github actions only # session.install_reqs(phase="codecov", phase_reqs=["codecov", "keyring"]) diff --git a/src/mkdocs_gallery/scrapers.py b/src/mkdocs_gallery/scrapers.py index 39476014..b8d40fe3 100644 --- a/src/mkdocs_gallery/scrapers.py +++ b/src/mkdocs_gallery/scrapers.py @@ -22,7 +22,7 @@ from pathlib import Path, PurePosixPath from textwrap import indent from typing import Dict, List, Optional -from warnings import filterwarnings +from warnings import filterwarnings, warn from .errors import ExtensionError from .gen_data_model import GalleryScript @@ -303,14 +303,18 @@ def mayavi_scraper(block, script: GalleryScript): The ReSTructuredText that will be rendered to HTML containing the images. This is often produced by :func:`figure_md_or_html`. """ - from mayavi import mlab + try: + from mayavi import mlab + except ModuleNotFoundError: + warn("No module named 'mayavi', skipping mayavi image scraper.") + return "" # skip scraper function image_path_iterator = script.run_vars.image_path_iterator image_paths = list() e = mlab.get_engine() for scene, image_path in zip(e.scenes, image_path_iterator): try: - mlab.savefig(image_path, figure=scene) + mlab.savefig(str(image_path), figure=scene) except Exception: mlab.close(all=True) raise