Skip to content

Commit 6894c4b

Browse files
larsonerclaude
andcommitted
perf: keep one browser running for the whole build
Each static image export launches and tears down Chrome via kaleido (~1.6 s per figure; rendering itself is ~50 ms). Start kaleido's global sync server once from the availability probe so every export in the build reuses one browser; kaleido stops it atexit. Carries the same kopts (plotlyjs/mathjax/headers defaults) the per-call path would pass, and scopes away the resulting 'kopts ignored' warning. Brings the sphinx-gallery demo build from ~7 s to ~1 s of plotly example time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent b120480 commit 6894c4b

2 files changed

Lines changed: 45 additions & 2 deletions

File tree

plotly/io/_sg_scraper.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import logging
77
import os
88
import textwrap
9+
import warnings
910

1011
import plotly
1112
from plotly.basedatatypes import BaseFigure
@@ -140,15 +141,42 @@ def _trailing_repr_figure(block, block_vars):
140141
return figure
141142

142143

144+
def _start_export_server():
145+
"""Keep one browser running for the whole build.
146+
147+
Without it, every static image export launches and tears down a browser
148+
(~1.5 s each); with it, only the first does (~50 ms each after that).
149+
Kaleido stops the server atexit.
150+
"""
151+
try:
152+
import kaleido
153+
154+
from plotly.io import defaults
155+
156+
# The options plotly.io.to_image would otherwise pass per export.
157+
kopts = {}
158+
if defaults.plotlyjs:
159+
kopts["plotlyjs"] = defaults.plotlyjs
160+
if defaults.mathjax:
161+
kopts["mathjax"] = defaults.mathjax
162+
if getattr(defaults, "headers", None):
163+
kopts["headers"] = defaults.headers
164+
kaleido.start_sync_server(silence_warnings=True, **kopts)
165+
except Exception:
166+
pass # Kaleido v0 keeps a persistent instance itself; the probe
167+
# reports any other problem
168+
169+
143170
@functools.lru_cache(maxsize=None) # functools.cache needs Python 3.9
144171
def _static_export_available():
145172
"""Whether static image export works, probed on the first scrape.
146173
147174
Cached so that a build without Kaleido or a browser warns once (per
148175
worker, for parallel sphinx-gallery builds) instead of once per figure.
149176
"""
177+
_start_export_server()
150178
try:
151-
plotly.io.to_image({"data": []}, format="png", validate=False)
179+
_export_image({"data": []}, None, "png")
152180
except Exception as exc:
153181
try:
154182
from sphinx.util.logging import getLogger
@@ -198,10 +226,23 @@ def _raw_html_rst(html):
198226
return "\n.. raw:: html\n\n" + textwrap.indent(html, " ") + "\n"
199227

200228

229+
def _export_image(fig_dict, file, image_format):
230+
"""Export one static image (to memory when `file` is None)."""
231+
with warnings.catch_warnings():
232+
# The kopts the export server was started with already apply
233+
warnings.filterwarnings(
234+
"ignore", message="The kopts argument", category=UserWarning
235+
)
236+
if file is None:
237+
plotly.io.to_image(fig_dict, format=image_format, validate=False)
238+
else:
239+
plotly.io.write_image(fig_dict, file, format=image_format, validate=False)
240+
241+
201242
def _write_image(fig_dict, file, image_format):
202243
"""Write a static image, with a helpful message if that is not possible."""
203244
try:
204-
plotly.io.write_image(fig_dict, file, format=image_format, validate=False)
245+
_export_image(fig_dict, file, image_format)
205246
except Exception as exc:
206247
raise RuntimeError(
207248
f"Writing {file} failed with:\n{type(exc).__name__}: {exc}\n"

tests/test_io/test_sg_scraper.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ def write_dummy_image(fig, file, format="png", **kwargs):
7070
monkeypatch.setattr(pio, "write_image", write_dummy_image)
7171
# Images come from the stand-in above, so the Kaleido probe must pass too
7272
monkeypatch.setattr(pio, "to_image", lambda *args, **kwargs: b"")
73+
# and no real browser should be launched by the tests
74+
monkeypatch.setattr(sg_scraper, "_start_export_server", lambda: None)
7375
sg_scraper._static_export_available.cache_clear()
7476

7577
example_dir = tmp_path / "auto_examples"

0 commit comments

Comments
 (0)