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

Appearance settings

Commit f0e716e

Browse filesBrowse files
larsonerlucyleeow
andauthored
BUG: Fix serialization with Sphinx 7.3 (#1289)
Co-authored-by: Lucy Liu <jliu176@gmail.com>
1 parent 0b243bf commit f0e716e
Copy full SHA for f0e716e
Expand file treeCollapse file tree

24 files changed

+722
-448
lines changed

‎.circleci/config.yml

Copy file name to clipboardExpand all lines: .circleci/config.yml
+7-2Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838
command: |
3939
pip install --upgrade --only-binary ":all:" pip setuptools
4040
pip install --upgrade --only-binary ":all:" \
41-
numpy matplotlib seaborn statsmodels pillow joblib sphinx pytest traits pyvista memory_profiler "ipython!=8.7.0" plotly graphviz "docutils>=0.18" imageio pydata-sphinx-theme \
41+
numpy matplotlib seaborn statsmodels pillow joblib "sphinx!=7.3.2,!=7.3.3,!=7.3.4,!=7.3.5,!=7.3.6" pytest traits pyvista memory_profiler "ipython!=8.7.0" plotly graphviz "docutils>=0.18" imageio pydata-sphinx-theme \
4242
"jupyterlite-sphinx>=0.8.0,<0.9.0" "jupyterlite-pyodide-kernel<0.1.0" libarchive-c
4343
pip uninstall -yq vtk # pyvista installs vtk above
4444
pip install --upgrade --only-binary ":all" --extra-index-url https://wheels.vtk.org vtk-osmesa
@@ -66,7 +66,12 @@ jobs:
6666
- attach_workspace:
6767
at: ~/
6868
- bash_env
69-
- run: sphinx-build doc doc/_build/html -nW --keep-going -b html
69+
- run: sphinx-build doc doc/_build/html -nW --keep-going -b html 2>&1 | tee sphinx_log.txt
70+
- run:
71+
name: Check sphinx log for warnings (which are treated as errors)
72+
when: always
73+
command: |
74+
! grep "^.* WARNING: .*$" sphinx_log.txt
7075
- store_artifacts:
7176
path: doc/_build/html/
7277
destination: html

‎.github/install.sh

Copy file name to clipboardExpand all lines: .github/install.sh
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ if [ "$SPHINX_VERSION" == "dev" ]; then
3131
PIP_DEPENDENCIES="--upgrade --pre https://api.github.com/repos/sphinx-doc/sphinx/zipball/master --default-timeout=60 --extra-index-url 'https://pypi.anaconda.org/scientific-python-nightly-wheels/simple' $PIP_DEPENDENCIES"
3232
elif [ "$SPHINX_VERSION" != "default" ]; then
3333
PIP_DEPENDENCIES="sphinx==${SPHINX_VERSION}.* $PIP_DEPENDENCIES"
34+
else
35+
PIP_DEPENDENCIES="sphinx!=7.3.2,!=7.3.3,!=7.3.4,!=7.3.5,!=7.3.6 $PIP_DEPENDENCIES"
3436
fi
3537

3638
set -x

‎.pre-commit-config.yaml

Copy file name to clipboardExpand all lines: .pre-commit-config.yaml
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ repos:
55
- id: ruff-format
66
exclude: plot_syntaxerror
77
- id: ruff
8+
args: ["--fix"]
89

910
- repo: https://github.com/codespell-project/codespell
1011
rev: v2.2.6

‎CHANGES.rst

Copy file name to clipboardExpand all lines: CHANGES.rst
+11Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
Changelog
22
=========
33

4+
v0.16.0
5+
-------
6+
Sphinx 7.3.0 and above changed caching and serialization checks. Now instead of passing
7+
instantiated classes like ``ResetArgv()``, classes like ``FileNameSortKey``, or
8+
callables like ``notebook_modification_function`` in ``sphinx_gallery_conf``,
9+
you should pass fully qualified name strings to classes or callables. If you change
10+
to using name strings, you can simply use a function as the use of classes to ensure
11+
a stable ``__repr__`` would be redundant.
12+
13+
See :ref:`importing_callables` for details.
14+
415
v0.15.0
516
-------
617

‎dev-requirements.txt

Copy file name to clipboardExpand all lines: dev-requirements.txt
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
sphinx >= 4, != 5.2.0
1+
# see https://github.com/sphinx-doc/sphinx/issues/12299
2+
sphinx>=4,!= 5.2.0,!=7.3.2,!=7.3.3,!=7.3.4,!=7.3.5,!=7.3.6
23
pydata-sphinx-theme
34
pytest
45
pytest-coverage

‎doc/advanced.rst

Copy file name to clipboardExpand all lines: doc/advanced.rst
+75-93Lines changed: 75 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -216,45 +216,43 @@ It uses an approach similar to what
216216
:func:`sphinx_gallery.scrapers.matplotlib_scraper` does under the hood, which
217217
use the helper function :func:`sphinx_gallery.scrapers.figure_rst` to
218218
create the standardized reST. If your package will be used to write an image
219-
file to disk (e.g., PNG or JPEG), we recommend you use a similar approach,
220-
but via a class so that the ``__repr__`` can remain stable across Sphinx runs::
219+
file to disk (e.g., PNG or JPEG), we recommend you use a similar approach::
221220

222-
class MyModuleScraper():
223-
def __repr__(self):
224-
return 'MyModuleScraper'
221+
def my_module_scraper(block, block_vars, gallery_conf):
222+
import mymodule
223+
# We use a list to collect references to image names
224+
image_names = list()
225+
# The `image_path_iterator` is created by Sphinx-Gallery, it will yield
226+
# a path to a file name that adheres to Sphinx-Gallery naming convention.
227+
image_path_iterator = block_vars['image_path_iterator']
225228

226-
def __call__(self, block, block_vars, gallery_conf):
227-
import mymodule
228-
# We use a list to collect references to image names
229-
image_names = list()
230-
# The `image_path_iterator` is created by Sphinx-Gallery, it will yield
231-
# a path to a file name that adheres to Sphinx-Gallery naming convention.
232-
image_path_iterator = block_vars['image_path_iterator']
229+
# Define a list of our already-created figure objects.
230+
list_of_my_figures = mymodule.get_figures()
233231

234-
# Define a list of our already-created figure objects.
235-
list_of_my_figures = mymodule.get_figures()
232+
# Iterate through figure objects, save to disk, and keep track of paths.
233+
for fig, image_path in zip(list_of_my_figures, image_path_iterator):
234+
fig.save_png(image_path)
235+
image_names.append(image_path)
236236

237-
# Iterate through figure objects, save to disk, and keep track of paths.
238-
for fig, image_path in zip(list_of_my_figures, image_path_iterator):
239-
fig.save_png(image_path)
240-
image_names.append(image_path)
237+
# Close all references to figures so they aren't used later.
238+
mymodule.close('all')
241239

242-
# Close all references to figures so they aren't used later.
243-
mymodule.close('all')
240+
# Use the `figure_rst` helper function to generate the reST for this
241+
# code block's figures. Alternatively you can define your own reST.
242+
return figure_rst(image_names, gallery_conf['src_dir'])
244243

245-
# Use the `figure_rst` helper function to generate the reST for this
246-
# code block's figures. Alternatively you can define your own reST.
247-
return figure_rst(image_names, gallery_conf['src_dir'])
248-
249-
This code would be defined either in your ``conf.py`` file, or as a module that
250-
you import into your ``conf.py`` file. The configuration needed to use this
251-
scraper would look like::
244+
This code could be defined either in your ``conf.py`` file, or as a module that
245+
you import into your ``conf.py`` file (see :ref:`importing_callables`).
246+
The configuration needed to use this scraper would look like::
252247

253248
sphinx_gallery_conf = {
254249
...
255-
'image_scrapers': ('matplotlib', MyModuleScraper()),
250+
'image_scrapers': ('matplotlib', "my_module._scraper.my_module_scraper"),
256251
}
257252

253+
Where Sphinx-Gallery will parse the string ``"my_module._scraper.my_module_scraper"``
254+
to import the callable function.
255+
258256
Example 2: detecting image files on disk
259257
----------------------------------------
260258

@@ -264,67 +262,55 @@ the reST needed to embed them in the documentation. Note that the example script
264262
will still need to be executed to scrape the files, but the images
265263
don't need to be produced during the execution.
266264

267-
We'll use a callable class in this case, and assume it is defined within your
268-
package in a module called ``scraper``. Here is the scraper code::
265+
We assume the function is defined within your
266+
package in a module called ``_scraper``. Here is the scraper code::
269267

270268
from glob import glob
271269
import shutil
272270
import os
273271
from sphinx_gallery.scrapers import figure_rst
274272

275-
class PNGScraper(object):
276-
def __init__(self):
277-
self.seen = set()
278-
279-
def __repr__(self):
280-
return 'PNGScraper'
281-
282-
def __call__(self, block, block_vars, gallery_conf):
283-
# Find all PNG files in the directory of this example.
284-
path_current_example = os.path.dirname(block_vars['src_file'])
285-
pngs = sorted(glob(os.path.join(path_current_example, '*.png')))
286-
287-
# Iterate through PNGs, copy them to the sphinx-gallery output directory
288-
image_names = list()
289-
image_path_iterator = block_vars['image_path_iterator']
290-
for png in pngs:
291-
if png not in self.seen:
292-
self.seen |= set(png)
293-
this_image_path = image_path_iterator.next()
294-
image_names.append(this_image_path)
295-
shutil.move(png, this_image_path)
296-
# Use the `figure_rst` helper function to generate reST for image files
297-
return figure_rst(image_names, gallery_conf['src_dir'])
298-
273+
def png_scraper(block, block_vars, gallery_conf):
274+
# Find all PNG files in the directory of this example.
275+
path_current_example = os.path.dirname(block_vars['src_file'])
276+
pngs = sorted(glob(os.path.join(path_current_example, '*.png')))
277+
278+
# Iterate through PNGs, copy them to the Sphinx-Gallery output directory
279+
image_names = list()
280+
image_path_iterator = block_vars['image_path_iterator']
281+
seen = set()
282+
for png in pngs:
283+
if png not in seen:
284+
seen |= set(png)
285+
this_image_path = image_path_iterator.next()
286+
image_names.append(this_image_path)
287+
shutil.move(png, this_image_path)
288+
# Use the `figure_rst` helper function to generate reST for image files
289+
return figure_rst(image_names, gallery_conf['src_dir'])
299290

300291
Then, in our ``conf.py`` file, we include the following code::
301292

302-
from mymodule import PNGScraper
303-
304293
sphinx_gallery_conf = {
305294
...
306-
'image_scrapers': ('matplotlib', PNGScraper()),
295+
'image_scrapers': ('matplotlib', 'my_module._scraper.png_scraper'),
307296
}
308297

309298
Example 3: matplotlib with SVG format
310299
-------------------------------------
311300
The :func:`sphinx_gallery.scrapers.matplotlib_scraper` supports ``**kwargs``
312301
to pass to :meth:`matplotlib.figure.Figure.savefig`, one of which is the
313302
``format`` argument. See :ref:`custom_scraper` for supported formats.
314-
To use SVG, you can do::
303+
To use SVG you can define the following function and ensure it is
304+
:ref:`importable <importing_callables>`::
315305

316-
from sphinx_gallery.scrapers import matplotlib_scraper
306+
def matplotlib_svg_scraper(*args, **kwargs):
307+
return matplotlib_scraper(*args, format='svg', **kwargs)
317308

318-
class matplotlib_svg_scraper(object):
319-
def __repr__(self):
320-
return self.__class__.__name__
321-
322-
def __call__(self, *args, **kwargs):
323-
return matplotlib_scraper(*args, format='svg', **kwargs)
309+
Then in your ``conf.py``::
324310

325311
sphinx_gallery_conf = {
326312
...
327-
'image_scrapers': (matplotlib_svg_scraper(),),
313+
'image_scrapers': ("sphinxext.matplotlib_svg_scraper",),
328314
...
329315
}
330316

@@ -335,35 +321,31 @@ writing a customized scraper class or function.
335321

336322
Example 4: Mayavi scraper
337323
-------------------------
338-
Historically, sphinx-gallery supported scraping Mayavi figures as well as
324+
Historically, Sphinx-Gallery supported scraping Mayavi figures as well as
339325
matplotlib figures. However, due to the complexity of maintaining the scraper,
340326
support was deprecated in version 0.12.0. To continue using a Mayavi scraping,
341327
consider using something like the following::
342328

343329
from sphinx_gallery.scrapers import figure_rst
344330

345-
class MayaviScraper():
346-
def __repr__(self):
347-
return 'MyModuleScraper'
348-
349-
def __call__(self, block, block_vars, gallery_conf):
350-
from mayavi import mlab
351-
image_path_iterator = block_vars['image_path_iterator']
352-
image_paths = list()
353-
e = mlab.get_engine()
354-
for scene, image_path in zip(e.scenes, image_path_iterator):
355-
try:
356-
mlab.savefig(image_path, figure=scene)
357-
except Exception:
358-
mlab.close(all=True)
359-
raise
360-
# make sure the image is not too large
361-
scale_image(image_path, image_path, 850, 999)
362-
if 'images' in gallery_conf['compress_images']:
363-
optipng(image_path, gallery_conf['compress_images_args'])
364-
image_paths.append(image_path)
365-
mlab.close(all=True)
366-
return figure_rst(image_paths, gallery_conf['src_dir'])
331+
def mayavi_scraper(self, block, block_vars, gallery_conf):
332+
from mayavi import mlab
333+
image_path_iterator = block_vars['image_path_iterator']
334+
image_paths = list()
335+
e = mlab.get_engine()
336+
for scene, image_path in zip(e.scenes, image_path_iterator):
337+
try:
338+
mlab.savefig(image_path, figure=scene)
339+
except Exception:
340+
mlab.close(all=True)
341+
raise
342+
# make sure the image is not too large
343+
scale_image(image_path, image_path, 850, 999)
344+
if 'images' in gallery_conf['compress_images']:
345+
optipng(image_path, gallery_conf['compress_images_args'])
346+
image_paths.append(image_path)
347+
mlab.close(all=True)
348+
return figure_rst(image_paths, gallery_conf['src_dir'])
367349

368350
Integrate custom scrapers with Sphinx-Gallery
369351
---------------------------------------------
@@ -421,14 +403,15 @@ For example, to reset matplotlib to always use the ``ggplot`` style, you could d
421403
style.use('ggplot')
422404

423405
Any custom functions can be defined (or imported) in ``conf.py`` and given to
424-
the ``reset_modules`` configuration key. To add the function defined above::
406+
the ``reset_modules`` configuration key. To add the function defined above (assuming
407+
you've make it :ref:`importable <importing_callables>`)::
425408

426409
sphinx_gallery_conf = {
427410
...
428-
'reset_modules': (reset_mpl, 'seaborn'),
411+
'reset_modules': ("sphinxext.reset_mpl", "seaborn"),
429412
}
430413

431-
In the config above ``'seaborn'`` refers to the native seaborn resetting
414+
In the config above ``"seaborn"`` refers to the native seaborn resetting
432415
function (see :ref:`reset_modules`).
433416

434417
.. note:: Using resetters such as ``reset_mpl`` that deviate from the
@@ -441,7 +424,6 @@ after an example, a function signature with three parameters can be used, where
441424
the third parameter is required to be named ``when``::
442425

443426
def reset_mpl(gallery_conf, fname, when):
444-
445427
import matplotlib as mpl
446428
mpl.rcParams['lines.linewidth'] = 2
447429
if when == 'after' and fname=='dashed_lines':

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.