Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Add a filename-prefix option to the Sphinx plot directive#28187

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
timhoffm merged 24 commits intomatplotlib:mainfromasmeurer:output-base-name
Jun 16, 2025

Conversation

asmeurer
Copy link
Contributor

@asmeurerasmeurer commentedMay 8, 2024
edited
Loading

This allows specifying the output base name of the generated image files.

This is required to use the plot directive with MyST, because the directive is broken with MyST (an issue I don't want to fix), requiring the use of eval-rst. But the way eval-rst works, the incrementing counter is not maintained across different eval-rst directives, meaning if you try to include multiple of them in the same document, the images will overwrite each other. This allows you to manually work around this with something like

```{eval-rst}.. plot::   :filename-prefix: plot-1   ...``````{eval-rst}.. plot::   :filename-prefix: plot-2   ...```

Aside from this, it's generally useful to be able to specify the image name used for a plot, as a more informative name can be used rather than just<document-name>-1.png.

PR summary

PR checklist

Copy link

@github-actionsgithub-actionsbot left a comment

Choose a reason for hiding this comment

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

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 uson gitter for real-time discussion.

For details on testing, writing docs, and our review process, please seethe developer guide

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

@tacaswelltacaswell added this to thev3.9.1 milestoneMay 8, 2024
@asmeurer
Copy link
ContributorAuthor

For anyone interested, the error you get if you try to use the plot directive with MyST directly isERROR: MockStateMachine has not yet implemented attribute 'insert_input'.

@asmeurer
Copy link
ContributorAuthor

Ah, I didn't realize there actually already are unit tests for this. Let me update that.

This allows specifying the output base name of the generated image files. Thename can include '{counter}', which is automatically string formatted to anincrementing counter. The default if it is not specified is left intact asthe current behavior, which is to use the base name of the provided script orthe RST document.This is required to use the plot directive with MyST, because the directive isbroken with MyST (an issue I don't want to fix), requiring the use ofeval-rst. But the way eval-rst works, the incrementing counter is notmaintained across different eval-rst directives, meaning if you try to includemultiple of them in the same document, the images will overwrite each other.This allows you to manually work around this with something like```{eval-rst}.. plot::   :output-base-name: plot-1   ...``````{eval-rst}.. plot::   :output-base-name: plot-2   ...```Aside from this, it's generally useful to be able to specify the image nameused for a plot, as a more informative name can be used rather than just'<document-name>-1.png'.
@asmeurer
Copy link
ContributorAuthor

For me, the Sphinx extension tests fail on main

        writing output... [ 20%] included_plot_21EE       stderr:EE       Exception occurred:E         File "/Users/aaronmeurer/miniconda3/envs/ndindex-matplotlib-dev/lib/python3.12/shutil.py", line 260, in copyfileE           with open(src, 'rb') as fsrc:E                ^^^^^^^^^^^^^^^E       FileNotFoundError: [Errno 2] No such file or directory: '/private/var/folders/wc/dppcpmxs1tlb36nqcw853wkm0000gn/T/pytest-of-aaronmeurer/pytest-2/test_srcset_version0/plot_directive/included_plot_21-1.2x.png'E       The full traceback has been saved in /var/folders/wc/dppcpmxs1tlb36nqcw853wkm0000gn/T/sphinx-err-nut6ujx8.log, if you want to report the issue to the developers.E       Please also report this if it was a user error, so that a better error message can be provided next time.E       A bug report can be filed in the tracker at <https://github.com/sphinx-doc/sphinx/issues>. Thanks!
# Platform:         darwin; (macOS-14.4.1-arm64-arm-64bit)# Sphinx version:   7.2.6# Python version:   3.12.3 (CPython)# Docutils version: 0.20.1# Jinja2 version:   3.1.4# Pygments version: 2.18.0# Last messages:#   #   copying static files...#   done#   copying extra files...#   done#   done#   �[2K#   writing output... [ 20%]#   included_plot_21#   # Loaded extensions:#   sphinx.ext.mathjax (7.2.6)#   alabaster (0.7.16)#   sphinxcontrib.applehelp (1.0.8)#   sphinxcontrib.devhelp (1.0.6)#   sphinxcontrib.htmlhelp (2.0.5)#   sphinxcontrib.serializinghtml (1.1.10)#   sphinxcontrib.qthelp (1.0.7)#   matplotlib.sphinxext.plot_directive (3.10.0.dev151+gce15014066)#   matplotlib.sphinxext.figmpl_directive (3.10.0.dev151+gce15014066)# Traceback:Traceback (most recent call last):  File "/Users/aaronmeurer/miniconda3/envs/ndindex-matplotlib-dev/lib/python3.12/site-packages/sphinx/cmd/build.py", line 298, in build_main    app.build(args.force_all, args.filenames)  File "/Users/aaronmeurer/miniconda3/envs/ndindex-matplotlib-dev/lib/python3.12/site-packages/sphinx/application.py", line 355, in build    self.builder.build_update()  File "/Users/aaronmeurer/miniconda3/envs/ndindex-matplotlib-dev/lib/python3.12/site-packages/sphinx/builders/__init__.py", line 293, in build_update    self.build(to_build,  File "/Users/aaronmeurer/miniconda3/envs/ndindex-matplotlib-dev/lib/python3.12/site-packages/sphinx/builders/__init__.py", line 363, in build    self.write(docnames, list(updated_docnames), method)  File "/Users/aaronmeurer/miniconda3/envs/ndindex-matplotlib-dev/lib/python3.12/site-packages/sphinx/builders/__init__.py", line 571, in write    self._write_serial(sorted(docnames))  File "/Users/aaronmeurer/miniconda3/envs/ndindex-matplotlib-dev/lib/python3.12/site-packages/sphinx/builders/__init__.py", line 581, in _write_serial    self.write_doc(docname, doctree)  File "/Users/aaronmeurer/miniconda3/envs/ndindex-matplotlib-dev/lib/python3.12/site-packages/sphinx/builders/html/__init__.py", line 649, in write_doc    self.docwriter.write(doctree, destination)  File "/Users/aaronmeurer/miniconda3/envs/ndindex-matplotlib-dev/lib/python3.12/site-packages/docutils/writers/__init__.py", line 80, in write    self.translate()  File "/Users/aaronmeurer/miniconda3/envs/ndindex-matplotlib-dev/lib/python3.12/site-packages/sphinx/writers/html.py", line 36, in translate    self.document.walkabout(visitor)  File "/Users/aaronmeurer/miniconda3/envs/ndindex-matplotlib-dev/lib/python3.12/site-packages/docutils/nodes.py", line 186, in walkabout    if child.walkabout(visitor):       ^^^^^^^^^^^^^^^^^^^^^^^^  File "/Users/aaronmeurer/miniconda3/envs/ndindex-matplotlib-dev/lib/python3.12/site-packages/docutils/nodes.py", line 178, in walkabout    visitor.dispatch_visit(self)  File "/Users/aaronmeurer/miniconda3/envs/ndindex-matplotlib-dev/lib/python3.12/site-packages/sphinx/util/docutils.py", line 582, in dispatch_visit    method(node)  File "/Users/aaronmeurer/Documents/matplotlib/lib/matplotlib/sphinxext/figmpl_directive.py", line 174, in visit_figmpl_html    imagedir, srcset, rel = _copy_images_figmpl(self, node)                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^  File "/Users/aaronmeurer/Documents/matplotlib/lib/matplotlib/sphinxext/figmpl_directive.py", line 163, in _copy_images_figmpl    shutil.copyfile(abspath, imagedir / name)  File "/Users/aaronmeurer/miniconda3/envs/ndindex-matplotlib-dev/lib/python3.12/shutil.py", line 260, in copyfile    with open(src, 'rb') as fsrc:         ^^^^^^^^^^^^^^^FileNotFoundError: [Errno 2] No such file or directory: '/private/var/folders/wc/dppcpmxs1tlb36nqcw853wkm0000gn/T/pytest-of-aaronmeurer/pytest-2/test_srcset_version0/plot_directive/included_plot_21-1.2x.png'

@asmeurer
Copy link
ContributorAuthor

I added a test that will hopefully work. I'm having difficulty getting some of the tests to run successfully locally right now, but I believe the asserts I added work. If CI fails I might need some help figuring out why things aren't working on my computer.

asmeurer added a commit to asmeurer/ndindex that referenced this pull requestMay 9, 2024
else:
source_file_name = rst_file
code = textwrap.dedent("\n".join(map(str, content)))
counter = document.attributes.get('_plot_counter', 0) + 1
document.attributes['_plot_counter'] = counter
base, ext = os.path.splitext(os.path.basename(source_file_name))
output_base = '%s-%d.py' % (base, counter)
if options['output-base-name']:
output_base = options['output-base-name'].format(counter=counter)
Copy link
Member

Choose a reason for hiding this comment

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

could we prefix in the script/rst file name here as well? If you have duplicate output names in the same rst file that is a quick search to find and fix, but if you have the collision across multiple rst files it could be much more annoying to find where they are colliding.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Well my thinking here is also that users should have full control over the filename that is produced. If we always inject something into the name, that won't be the case. For instance, if you right-click and save a plot, this will be the filename used for the image.

Really, we need to figure out how to error if the same name is used twice. My naive idea of checking if the file already exists doesn't work because it could just exist from a previous build. It's probably possible to keep a global list of already used filenames, but I'll have to figure out how to make that work with partial Sphinx rebuilds.

@tacaswell
Copy link
Member

What version of pytest are you using? I could not reproduce this issue last night.

@tacaswelltacaswell modified the milestones:v3.9.1,v3.10.0May 9, 2024
@tacaswell
Copy link
Member

I am 👍 on this idea, one small question about the naming (I see both sides of forcing an additional prefix or not).

I re-milestoned this for 3.10 as it is a new feature which seems too big to put in via a micro release and it is too late to get this is under the wire for 3.9.

@asmeurer
Copy link
ContributorAuthor

What version of pytest are you using? I could not reproduce this issue last night.

It's possible my local build of matplotlib is broken somehow. pytest also does a really annoying thing when I run this test on my Mac where it switches to another space like it is opening a GUI window. I also had to run

sudo mkdir -p /tmp/.X11-unixsudo chmod 1777 /tmp/.X11-unix

or the tests would just error completely with some strange error (which fortunately ChatGPT was able to figure out).

I tried my best to follow your dev instructions, but some of the steps didn't work (e.g., the dev requirements.txt doesn't actually list any build requirements).

@tacaswell
Copy link
Member

The reason I suspect pytest is this path:'/private/var/folders/wc/dppcpmxs1tlb36nqcw853wkm0000gn/T/pytest-of-aaronmeurer/pytest-2/test_srcset_version0/plot_directive/included_plot_21-1.2x.png'

What was the strange error? I'm really not sure why you need X11 stuff for the tests of the sphinx extension to run...


We have an open PR to fix the dev install instructions. The conda environment file works, most of the regular devs who don't use conda know what to do, and we explicitly install them on CI so it fell through the cracks.

tacaswell
tacaswell previously approved these changesMay 9, 2024
@asmeurer
Copy link
ContributorAuthor

asmeurer commentedMay 9, 2024
edited
Loading

Sorry that I wasn't clear. I reproduced the Sphinx issue outside of pytest, using

cd lib/matplotlib/tests/tinypagespython -m sphinx -W -b html -d doctrees . _build/html/ -D plot_srcset=2x

The first output I showed is from pytest (which is running that command). The second is the log file from running the command directly, which shows the full traceback.

@asmeurer
Copy link
ContributorAuthor

Ah, so looking closer athttps://matplotlib.org/devdocs/devel/development_setup.html#create-a-dedicated-environment, I see there is a secret "conda" button you have to click. I really don't like that Sphinx extension that hides content behind tabs...

@tacaswell
Copy link
Member

oh, I bet our tiny pages config needs the backend set toAgg...

@asmeurer
Copy link
ContributorAuthor

The extension supposedly does that

But perhaps my private matplotlib config is messing with things.

@asmeurer
Copy link
ContributorAuthor

asmeurer commentedMay 9, 2024
edited
Loading

I have some work-in-progress code to do the duplicate filename checks. I'm trying to figure out why thisout_of_date function is needed

defout_of_date(original,derived,includes=None):
"""
Return whether *derived* is out-of-date relative to *original* or any of
the RST files included in it using the RST include directive (*includes*).
*derived* and *original* are full paths, and *includes* is optionally a
list of full paths which may have been included in the *original*.
"""
Shouldn't Sphinx already handle that logic itself? It apparently is needed based on this issue#17860, but I'm not understanding why.

@asmeurer
Copy link
ContributorAuthor

By the way, I saw you approved this. If you want to merge this as-is, that's fine. I can add the duplicate filename check in a separate PR. I'm still not completely sure yet if I can actually get it working.

@asmeurer
Copy link
ContributorAuthor

So it occurs to me that this might actually do unexpected things if a global output_base_name is set when there are partial rebuilds. I think I might just remove the ability to have a global option, as well as thecounter. We can at least check that no names are the same document.

@tacaswell
Copy link
Member

This is also going to do funny looking things if you mix plots with and without set base names as the counter is always increased so I think you will get names like [doc-1.png,my_name.png,doc-3.png].

One option would be to the'{counter}' logic and move the counter incrementing into the other branch.

Another option would be to instead of thinking of this as setting the base name, change the user facing knob to be a postfix so either we append a counterOR we stick on what ever the user gave us. I think that will fix the myst problem and prevents inter-document name collisions.

@tacaswelltacaswell dismissed theirstale reviewMay 13, 2024 18:42

Too enthusiastic, still some open questions.

@asmeurer
Copy link
ContributorAuthor

My plan is to simplify this considerably:

  • Remove the global base-name flag, and also the{counter} thing. Just have:output-base-name: and that's it. Is it possible for a singleplot directive to produce multiple files? If so, I'll still need to automatically append a counter in those cases. I think the issue you pointed out is accurate so I'll need to update the counter to work per-base name. If that's not possible, we can just ignore the counter completely when base-name is set.

  • Add a basic check for duplicate names in the same file. Checking for duplicate names across files is doable but annoying because we have to store information in the Sphinx environment so that it works across rebuilds.

@QuLogic
Copy link
Member

I can't comment on the line directly since it's not part of the diff, but theoutput_base variable that was changed is also used forshow-source-link, which doesn't look documented or tested (and thus I'm not sure if that was intentional either.)

@asmeurer
Copy link
ContributorAuthor

I added a test for multiple figures. I can confirm that it does work by testing manually, but for some reason adding leads to a much earlier test failing.

tmp_path = PosixPath('/private/var/folders/wc/dppcpmxs1tlb36nqcw853wkm0000gn/T/pytest-of-aaronmeurer/pytest-6/test_tinypages0')    def test_tinypages(tmp_path):        shutil.copytree(tinypages, tmp_path, dirs_exist_ok=True)        html_dir = tmp_path / '_build' / 'html'        img_dir = html_dir / '_images'        doctree_dir = tmp_path / 'doctrees'        # Build the pages with warnings turned into errors        build_sphinx_html(tmp_path, doctree_dir, html_dir)        def plot_file(num):            return img_dir / f'some_plots-{num}.png'        def plot_directive_file(num):            # This is always next to the doctree dir.            return doctree_dir.parent / 'plot_directive' / f'some_plots-{num}.png'        range_10, range_6, range_4 = (plot_file(i) for i in range(1, 4))        # Plot 5 is range(6) plot        assert filecmp.cmp(range_6, plot_file(5))        # Plot 7 is range(4) plot        assert filecmp.cmp(range_4, plot_file(7))        # Plot 11 is range(10) plot        assert filecmp.cmp(range_10, plot_file(11))        # Plot 12 uses the old range(10) figure and the new range(6) figure        assert filecmp.cmp(range_10, plot_file('12_00'))        assert filecmp.cmp(range_6, plot_file('12_01'))        # Plot 13 shows close-figs in action        assert filecmp.cmp(range_4, plot_file(13))        # Plot 14 has included source        html_contents = (html_dir / 'some_plots.html').read_text(encoding='utf-8')        assert '# Only a comment' in html_contents        # check plot defined in external file.>       assert filecmp.cmp(range_4, img_dir / 'range4.png')E       AssertionError: assert FalseE        +  where False = <function cmp at 0x12710b560>(PosixPath('/private/var/folders/wc/dppcpmxs1tlb36nqcw853wkm0000gn/T/pytest-of-aaronmeurer/pytest-6/test_tinypages0/_build/html/_images/some_plots-3.png'), (PosixPath('/private/var/folders/wc/dppcpmxs1tlb36nqcw853wkm0000gn/T/pytest-of-aaronmeurer/pytest-6/test_tinypages0/_build/html/_images') / 'range4.png'))E        +    where <function cmp at 0x12710b560> = filecmp.cmplib/matplotlib/tests/test_sphinxext.py:75: AssertionError

@asmeurer
Copy link
ContributorAuthor

Good catch on the source link. It is working, but it looks like it isn't properly adding the.py extension when there is a custom name. I'm working on a fix.

@asmeurer
Copy link
ContributorAuthor

Oh I think that was just because I was testing tinypages locally and it was messing with the tests.

…docs in tinypagesThe tests now don't copy any of the build files, meaning the tests should passeven if tinypages has stale build state from a manual build.
@asmeurer
Copy link
ContributorAuthor

OK, this is ready for review again.

Copy link
Member

@QuLogicQuLogic left a comment

Choose a reason for hiding this comment

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

Since this also affects the naming of the source file, should we go back tooutput-basename, or are you comfortable with it stayingimage-basename,@timhoffm?

@timhoffm
Copy link
Member

Since this also affects the naming of the source file, should we go back tooutput-basename, or are you comfortable with it stayingimage-basename,@timhoffm?

I don't care too much betweenoutput andimage. One can argue both ways: The source and the image use this, or we focus on the eventually desired output (the image) and the source just follows along with it.

We should consider whetherbasename is appropriate. Whileos.path.basename includes the extension, here it's not included.stem would be the technically correct term. However I feeloutput-stem orimage-stem is not instructive; we'd need to go with the lengthyoutput-filename-stem orimage-filename-stem. Maybe*-prefix would be another alternative.

Either way, I believe the logic is too complex to caputure everything in a single name. Until now, everything was magically determined behind the scenes (i.e. users should not have to bother with the exact name). If we now make the name configurable, we should explain what exactly this is in the associated docs

(1) this must not have any path components
(2) it may be the full filename stem, or only a prefix since AFAICS there are multiple ways that additional components could be appended.

@asmeurer
Copy link
ContributorAuthor

My original version did have something more configurable (the user could specify the format string), but I decided that was way too complex for what was needed here. I think the main thing here is that the user has some way to make the name a little more human readable. If it also adds some numbering or file extensions to that it isn't a problem, especially given that no one else has even raised this as a problem before. And obviously for my original problem the name itself doesn't even matter; it just needs to be unique.

@timhoffm
Copy link
Member

Then possiblyoutput-prefix orimage-prefix are good names as they only claim to define a part of the filename.

@asmeurer
Copy link
ContributorAuthor

FWIW, none of the suggested names really make a ton of sense if you just see them in aplot directive without the context of the sphinx extension internals, or even just the context of the matplotlib sphinx extension documentation. Perhaps the name should just be something likeoutput-name orfilename-prefix that is a little more self-documenting.

@timhoffm
Copy link
Member

filename-prefix sounds good to me.

@asmeurerasmeurer changed the titleAdd an image-basename option to the Sphinx plot directiveAdd an filename-prefix option to the Sphinx plot directiveJun 8, 2025
@asmeurer
Copy link
ContributorAuthor

OK, I've updated it to usefilename-prefix.

timhoffm reacted with thumbs up emoji

@QuLogicQuLogic changed the titleAdd an filename-prefix option to the Sphinx plot directiveAdd a filename-prefix option to the Sphinx plot directiveJun 11, 2025
@timhoffm
Copy link
Member

Failing tests should be fixed by#30162.@asmeurer can you please rebase on main?

@timhoffmtimhoffm merged commit80486e6 intomatplotlib:mainJun 16, 2025
38 of 40 checks passed
@timhoffm
Copy link
Member

Thanks@asmeurer

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

@github-actionsgithub-actions[bot]github-actions[bot] left review comments

@QuLogicQuLogicQuLogic approved these changes

@timhoffmtimhoffmtimhoffm approved these changes

@tacaswelltacaswelltacaswell left review comments

Assignees
No one assigned
Projects
None yet
Milestone
v3.11.0
Development

Successfully merging this pull request may close these issues.

6 participants
@asmeurer@tacaswell@ksunden@timhoffm@jklymak@QuLogic

[8]ページ先頭

©2009-2025 Movatter.jp