|
6 | 6 | import logging |
7 | 7 | import os |
8 | 8 | import textwrap |
| 9 | +import warnings |
9 | 10 |
|
10 | 11 | import plotly |
11 | 12 | from plotly.basedatatypes import BaseFigure |
@@ -140,15 +141,42 @@ def _trailing_repr_figure(block, block_vars): |
140 | 141 | return figure |
141 | 142 |
|
142 | 143 |
|
| 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 | + |
143 | 170 | @functools.lru_cache(maxsize=None) # functools.cache needs Python 3.9 |
144 | 171 | def _static_export_available(): |
145 | 172 | """Whether static image export works, probed on the first scrape. |
146 | 173 |
|
147 | 174 | Cached so that a build without Kaleido or a browser warns once (per |
148 | 175 | worker, for parallel sphinx-gallery builds) instead of once per figure. |
149 | 176 | """ |
| 177 | + _start_export_server() |
150 | 178 | try: |
151 | | - plotly.io.to_image({"data": []}, format="png", validate=False) |
| 179 | + _export_image({"data": []}, None, "png") |
152 | 180 | except Exception as exc: |
153 | 181 | try: |
154 | 182 | from sphinx.util.logging import getLogger |
@@ -198,10 +226,23 @@ def _raw_html_rst(html): |
198 | 226 | return "\n.. raw:: html\n\n" + textwrap.indent(html, " ") + "\n" |
199 | 227 |
|
200 | 228 |
|
| 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 | + |
201 | 242 | def _write_image(fig_dict, file, image_format): |
202 | 243 | """Write a static image, with a helpful message if that is not possible.""" |
203 | 244 | try: |
204 | | - plotly.io.write_image(fig_dict, file, format=image_format, validate=False) |
| 245 | + _export_image(fig_dict, file, image_format) |
205 | 246 | except Exception as exc: |
206 | 247 | raise RuntimeError( |
207 | 248 | f"Writing {file} failed with:\n{type(exc).__name__}: {exc}\n" |
|
0 commit comments