Skip to content

Adds plot_exclude_patterns config to selectively disable plot_directive.#31270

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

Adds plot_exclude_patterns config to selectively disable plot_directive.#31270
billbrod wants to merge 10 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.

Comment thread lib/matplotlib/tests/test_sphinxext.py Outdated
Comment thread lib/matplotlib/tests/test_sphinxext.py Outdated
@billbrod
Copy link
Copy Markdown
Author

billbrod commented Apr 8, 2026

Responded to your comments

QuLogic
QuLogic previously approved these changes Apr 9, 2026
@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.

Comment thread lib/matplotlib/tests/test_sphinxext.py
@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.

@tacaswell
Copy link
Copy Markdown
Member

Sorry to come in after must of the discussion is done, but I have some skepticism of this, particularly as the usecase is to temporarily edit the conf.py files during development.

Would controlling this via an ENV be better (as that is easier to ensure is transient and not accidentally committed)? Do we need per-file or just "please turn off all plots" enough? If the goal is to opt-in to only running a single plot directive would an "must match" test be better?

Comment thread lib/matplotlib/tests/test_sphinxext.py Outdated
assert st in (html_dir / 'nestedpage2/index.html').read_text(encoding='utf-8')


@pytest.mark.parametrize('plot_exclude_patterns', [False, "*index*",
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.

At some level this is really testing the functionality of Path.match which I am happy to assume works as documented. I think just one is enough in this case (or maybe one with 1 and one with multiple).

@QuLogic
Copy link
Copy Markdown
Member

QuLogic commented Apr 13, 2026

particularly as the usecase is to temporarily edit the conf.py files during development.

That's not necessary for Sphinx options; you can pass -Dplot_exclude_patterns=foo*,bar to sphinx-build to override conf.py.

@billbrod
Copy link
Copy Markdown
Author

For the tests: the fact that I was testing so many variations is I think a legacy of the fact that I initially was using regex and wanted to make sure I understood how it worked. I agree that now that I'm using pathlib.match, that it's unnecessary to test so many variants.

I also agree that a mark would be better (much easier to use) and an overall "Skip all" would be sufficient for my use case, and it might be preferable to keep it really simple. I'm not familiar with that many examples using a decorator with Sphinx, only Deprecated. If we want to go that route, are there other packages you're familiar with that I can look at as examples?

particularly as the usecase is to temporarily edit the conf.py files during development.

I see this functionality being used by having some logic in conf.py that checks an environment variable or using the -D option that @QuLogic mentioned.

How should I proceed on this? Like I said, I think either of your suggestions (mark/decorator or some sort of global "skip all") work well, and are simpler to use / explain than my current solution, so are preferable. I defer to you all as to what makes the most sense.

@timhoffm
Copy link
Copy Markdown
Member

If skipall is sufficient to you, let's just do that. That's much simpler than adding markers and we can always add markers later should the need arise.

@billbrod billbrod force-pushed the sphinxext-optional branch from b67b208 to 0a1931b Compare April 14, 2026 19:55
@billbrod
Copy link
Copy Markdown
Author

Okay, I went ahead and switched this to now be plot_skip_all, a boolean that just skips all plot directives, rebased onto main, and made the test a single test.

I kept the logic at the same place, so that the only thing that is skipped is running the code (all the machinery around captions, show code, etc. are unaffected). Happy to move it if that would be better.

@QuLogic QuLogic dismissed their stale review April 14, 2026 21:03

New implementation

@tacaswell
Copy link
Copy Markdown
Member

Can you please also add a whats_new entry including how to use this from the command line (as @QuLogic noted above)?

@billbrod
Copy link
Copy Markdown
Author

Can you please also add a whats_new entry including how to use this from the command line (as @QuLogic noted above)?

Done, let me know if you have any comments on it. I also added .. versionadded:: 3.11 to the parameter, let me know if that was incorrect.

This made me realize maybe plot_skip_all isn't the best name? Maybe plot_disable or plot_tmp_disable or something?

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.

5 participants