Skip to content

Adds plot_exclude_patterns config to selectively disable plot_directive.#31270

Open
billbrod wants to merge 8 commits intomatplotlib:mainfrom
billbrod:sphinxext-optional
Open

Adds plot_exclude_patterns config to selectively disable plot_directive.#31270
billbrod wants to merge 8 commits intomatplotlib:mainfrom
billbrod:sphinxext-optional

Conversation

@billbrod
Copy link
Copy Markdown

PR summary

I opened a discussion on discourse asking whether it was possible to temporarily disable the plot_directive, and was told by @timhoffm that "There is currently no clean way of doing this. One could add a config value. It’s not important enough that I’d work on this myself, but I’d accept a PR for it." This is my attempt at that change.

Inspired by myst-nb's nb_execution_excludepatterns config option, this allows users to specify in their sphinx configuration a list of patterns to selectively avoid running some plot directives. The goal is to speed up build time when developing, especially when making changes to project documentation. It does this in a simple way, by checking re.match against the output_base for the created plots for all patterns in the list. If any match, we do not run the code, set result = [(code, [])], and continue. Setting result in this way makes it so the appearance in the built docs is the same as if an error was encountered: code is displayed if so configured, no associated plot.

  • I had originally wanted to check against function_name or something more specific, but it looks like that is often not visible to the sphinx extension, especially when building examples found in docstrings (which is my most common use case).
  • I had considered using re.fullmatch against output_base, but it seems to me that the full output_base might be difficult for a user to predict. Using just re.match may be over-zealous, but given that this is intended to speed up builds during development (not during production), false positives seem more acceptable to me.
  • I figure regex match is the best way to match patterns here. I think glob patterns (like in nb_execution_excludepatterns) are most common for filenames, and the use of re.match means that simple matches (e.g., just "plot") will function as a substring check, while still allowing for more complicated logic.
  • I named this plot_exclude_patterns (with the underscore) rather than plot_excludepatterns (which would match myst-nb's value) to be more consistent with the naming pattern of the other config values.
  • Having this check where it is feels like it might be inefficient to me, but it's not clear how much of the other plot directive code can be skipped without causing other problems. Happy to move it somewhere else if it belongs in a different location!

AI Disclosure

No LLMs were used in this contribution.

PR checklist

  • [N/A] "closes #0000" is in the body of the PR description to link the related issue
  • new and changed code is tested
  • [N/A] Plotting related features are demonstrated in an example
  • [N/A] New Features and API Changes are noted with a directive and release note
  • Documentation complies with general and docstring guidelines
    • I think the only place to edit is the module-level docstring in plot_directive.py, from grepping for the other config values.

@github-actions
Copy link
Copy Markdown

Thank you for opening your first PR into Matplotlib!

If you have not heard from us in a week or so, please leave a new comment below and that should bring it to our attention. Most of our reviewers are volunteers and sometimes things fall through the cracks.

You can also join us on gitter for real-time discussion.

For details on testing, writing docs, and our review process, please see the developer guide.
Please let us know if (and how) you use AI, it will help us give you better feedback on your PR.

We strive to be a welcoming and open project. Please follow our Code of Conduct.

error will be raised.

plot_exclude_patterns
List of regex patterns to check against to selectively skip plot
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My standard question when matching file patterns 😄: Do you really want regular expressions or are wildcard patterns (fnmatch) sufficient/better?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not aware of fnmatch -- that looks sufficient (and easier) to me! I'll update the PR to use that instead.


plot_exclude_patterns
List of regex patterns to check against to selectively skip plot
directives. If any pattern matches the name of the file containing the
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is filename enough or do we need a relative path?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh relative path might be better. That increases the chance of false positive matches, but makes it easier to do a simple "disable all plot directives found in the API documentation", which is nice. I'll do that

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@melissawm melissawm moved this to Waiting for author in First Time Contributors Mar 11, 2026
@billbrod
Copy link
Copy Markdown
Author

Pushed changes following @timhoffm suggestions.

Some questions this raised for me:

  • Should there be examples of showing some matches in the user-facing docs somewhere? Given that "matching" works differently for fnmatch and regex (though fnmatch behaves the same as glob, right?), an example or two might make expected behavior more obvious. If so, does this belong right in the docstring of plot_directive.py or is there somewhere else?
  • Related to the above, using fnmatch means that you'll have to use a lot of wildcards. This is fine, but took me a second to realize as I switched from regex. Maybe that will come naturally to users who aren't first thinking in regex, but I wanted to flag it.

@billbrod
Copy link
Copy Markdown
Author

The failing tests look unrelated to my changes, should I try and fix them?

@timhoffm
Copy link
Copy Markdown
Member

Yes the failing tests are unrelated.

On "a lot of wildcards": As mentioned above, you may also want to check https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.full_match and https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.match, which may be more convenient than fnmatch.

@billbrod
Copy link
Copy Markdown
Author

billbrod commented Mar 20, 2026

Sorry this took me a bit, I was at a conference.

It looks like full_match was only added in python 3.13, so we shouldn't use that, right? (As matplotlib supports 3.10 and up.)

match looks like a good solution, so I've gone ahead and changed this to use that method instead.

@melissawm melissawm moved this from Waiting for author to Needs review in First Time Contributors Mar 23, 2026
@billbrod
Copy link
Copy Markdown
Author

billbrod commented Apr 6, 2026

Hi all, wanted to bump this.

@billbrod
Copy link
Copy Markdown
Author

billbrod commented Apr 8, 2026

Responded to your comments

@melissawm melissawm moved this from Needs review to Needs decision in First Time Contributors Apr 9, 2026
@QuLogic
Copy link
Copy Markdown
Member

QuLogic commented Apr 9, 2026

Please rebase so that the doc builds work.

@billbrod
Copy link
Copy Markdown
Author

billbrod commented Apr 9, 2026

I believe I did the rebase, let me know if I did it incorrectly

Comment on lines +166 to +168
List of non-recursive glob-style patterns to check against to selectively
skip plot directives. If any pattern matches the relative path of the file
containing the plot directive, then that plot directive will be skipped.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
List of non-recursive glob-style patterns to check against to selectively
skip plot directives. If any pattern matches the relative path of the file
containing the plot directive, then that plot directive will be skipped.
List of non-recursive glob-style patterns for selectively skipping plot
directives. If any pattern matches the relative path of a documentation
file, then plot directives in that file will be skipped.

I'm not clear, but assume this works only for documentation source files (i.e. .rst files), not for plot directives in docstrings and not for sphinx-gallery input files. Is that correct?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works for all .rst files, but that includes plot directives in docstrings, at least in my setup. I'm using sphinx autosummary for my API docs, which generates rst files that sphinx then uses.

I am not sure about sphinx-gallery, since I don't use that package. I'll try testing this on a minimal example and see how that goes.

else:
extra_args = []
# Build the pages with warnings turned into errors
build_sphinx_html(tmp_path, doctree_dir, html_dir,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm slightly worried on the runtime cost 12x (parametrized) building a documentation seems quite expensive. Have you tested locally how long this test takes?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I run this locally with pytest lib/matplotlib/tests/test_sphinxext.py -k 'exclude_patterns' -n 0, it takes a total of 23 seconds, with the call to each test taking 1.5 to 3 seconds:

image

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is on my laptop, not a workstation or cluster

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate the rigor of your testing, but that’s is too much. Imagine a developer runs the whole test suite, they’ll have to wait 23s longer just so that this comparably niche feature is tested.

Please condense to one test with a single documentation build. A basic test that this works is sufficient. By adding some variants to the list you can still test the most relevant cases: exclude by full filename, exclude full directory, exclude files with a specific pattern, e.g. a specific prefix.

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
@billbrod
Copy link
Copy Markdown
Author

billbrod commented Apr 10, 2026

Following up on

I'm not clear, but assume this works only for documentation source files (i.e. .rst files), not for plot directives in docstrings and not for sphinx-gallery input files. Is that correct?

This works on all three possibilities, as can be seen in my minimal working example. In conf.py, you can see the options required to skip the different possibilities (that should be runnable, I ran it with uv run make html -C docs from the root directory)

The wrinkle here, and this is something we went back and forth on, is that it matches the name of the path (in pathlib's terms), so it excludes all preceding directories. The easiest way to figure out the names to match, for me, was to do a build excluding nothing, and then look at the plot_directive/ folder in the sphinx build directory.

For my MWE, that's docs/_build/plot_directive, which looks like:
image

and you can see the three names to match there:

  • mpl_sphinxext_test: docstring in the package, because I put it in mpl_sphinxext_test/__init__.py
  • test_alignment: sphinx-gallery, that's the name of the .py file in the gallery's example directory
  • index: rst file in the docs, that's the name of the file itself.

Properly, you can look at the .rst files produced by your apidocs generator and sphinx-gallery:
image

What this doesn't allow you to match, which might be surprising to users is:

  • The containing directory (so you can't match api/ or auto_examples/ to exclude all of sphinx-gallery or API docs), because of how pathlib match works
  • The function name for API docs, since that doesn't get represented in the file name (if you use another package to generate your API docs, it's possible it would; here I used sphinx.ext.apidoc, which uses sphinx.ext.autosummary)

@timhoffm
Copy link
Copy Markdown
Member

timhoffm commented Apr 11, 2026

So to clarify: This always uses the .rst file from which the docs are rendered. If the plot directive is in a Python file, e.g. docstrings or Sphinx-gallery example, the .rst filename and location depends on the respective tooling that generates the intermediate .rst representation.

Is this correct? If so, that should be added to the description of the option.

@timhoffm
Copy link
Copy Markdown
Member

On a general note: is filename a good (I.e. easily usable) filter? The alternative could be to give authors the ability to give plots markers and filter on them, similar to pytest markers. In the simplest case you could have a “slow” marker and just exclude them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Needs decision

Development

Successfully merging this pull request may close these issues.

4 participants