##// END OF EJS Templates
Don't force matplotlib backend names to be lowercase
Ian Thomas -
Show More
@@ -1,173 +1,173 b''
1 1 """Implementation of magic functions for matplotlib/pylab support.
2 2 """
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (c) 2012 The IPython Development Team.
5 5 #
6 6 # Distributed under the terms of the Modified BSD License.
7 7 #
8 8 # The full license is in the file COPYING.txt, distributed with this software.
9 9 #-----------------------------------------------------------------------------
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Imports
13 13 #-----------------------------------------------------------------------------
14 14
15 15 # Our own packages
16 16 from traitlets.config.application import Application
17 17 from IPython.core import magic_arguments
18 18 from IPython.core.magic import Magics, magics_class, line_magic
19 19 from IPython.testing.skipdoctest import skip_doctest
20 20 from warnings import warn
21 21
22 22 #-----------------------------------------------------------------------------
23 23 # Magic implementation classes
24 24 #-----------------------------------------------------------------------------
25 25
26 26 magic_gui_arg = magic_arguments.argument(
27 27 "gui",
28 28 nargs="?",
29 29 help="""Name of the matplotlib backend to use such as 'qt' or 'widget'.
30 30 If given, the corresponding matplotlib backend is used,
31 31 otherwise it will be matplotlib's default
32 32 (which you can set in your matplotlib config file).
33 33 """,
34 34 )
35 35
36 36
37 37 @magics_class
38 38 class PylabMagics(Magics):
39 39 """Magics related to matplotlib's pylab support"""
40 40
41 41 @skip_doctest
42 42 @line_magic
43 43 @magic_arguments.magic_arguments()
44 44 @magic_arguments.argument('-l', '--list', action='store_true',
45 45 help='Show available matplotlib backends')
46 46 @magic_gui_arg
47 47 def matplotlib(self, line=''):
48 48 """Set up matplotlib to work interactively.
49 49
50 50 This function lets you activate matplotlib interactive support
51 51 at any point during an IPython session. It does not import anything
52 52 into the interactive namespace.
53 53
54 54 If you are using the inline matplotlib backend in the IPython Notebook
55 55 you can set which figure formats are enabled using the following::
56 56
57 57 In [1]: from matplotlib_inline.backend_inline import set_matplotlib_formats
58 58
59 59 In [2]: set_matplotlib_formats('pdf', 'svg')
60 60
61 61 The default for inline figures sets `bbox_inches` to 'tight'. This can
62 62 cause discrepancies between the displayed image and the identical
63 63 image created using `savefig`. This behavior can be disabled using the
64 64 `%config` magic::
65 65
66 66 In [3]: %config InlineBackend.print_figure_kwargs = {'bbox_inches':None}
67 67
68 68 In addition, see the docstrings of
69 69 `matplotlib_inline.backend_inline.set_matplotlib_formats` and
70 70 `matplotlib_inline.backend_inline.set_matplotlib_close` for more information on
71 71 changing additional behaviors of the inline backend.
72 72
73 73 Examples
74 74 --------
75 75 To enable the inline backend for usage with the IPython Notebook::
76 76
77 77 In [1]: %matplotlib inline
78 78
79 79 In this case, where the matplotlib default is TkAgg::
80 80
81 81 In [2]: %matplotlib
82 82 Using matplotlib backend: TkAgg
83 83
84 84 But you can explicitly request a different GUI backend::
85 85
86 86 In [3]: %matplotlib qt
87 87
88 88 You can list the available backends using the -l/--list option::
89 89
90 90 In [4]: %matplotlib --list
91 91 Available matplotlib backends: ['osx', 'qt4', 'qt5', 'gtk3', 'gtk4', 'notebook', 'wx', 'qt', 'nbagg',
92 92 'gtk', 'tk', 'inline']
93 93 """
94 94 args = magic_arguments.parse_argstring(self.matplotlib, line)
95 95 if args.list:
96 96 from IPython.core.pylabtools import _list_matplotlib_backends_and_gui_loops
97 97
98 98 print(
99 99 "Available matplotlib backends: %s"
100 100 % _list_matplotlib_backends_and_gui_loops()
101 101 )
102 102 else:
103 gui, backend = self.shell.enable_matplotlib(args.gui.lower() if isinstance(args.gui, str) else args.gui)
103 gui, backend = self.shell.enable_matplotlib(args.gui)
104 104 self._show_matplotlib_backend(args.gui, backend)
105 105
106 106 @skip_doctest
107 107 @line_magic
108 108 @magic_arguments.magic_arguments()
109 109 @magic_arguments.argument(
110 110 '--no-import-all', action='store_true', default=None,
111 111 help="""Prevent IPython from performing ``import *`` into the interactive namespace.
112 112
113 113 You can govern the default behavior of this flag with the
114 114 InteractiveShellApp.pylab_import_all configurable.
115 115 """
116 116 )
117 117 @magic_gui_arg
118 118 def pylab(self, line=''):
119 119 """Load numpy and matplotlib to work interactively.
120 120
121 121 This function lets you activate pylab (matplotlib, numpy and
122 122 interactive support) at any point during an IPython session.
123 123
124 124 %pylab makes the following imports::
125 125
126 126 import numpy
127 127 import matplotlib
128 128 from matplotlib import pylab, mlab, pyplot
129 129 np = numpy
130 130 plt = pyplot
131 131
132 132 from IPython.display import display
133 133 from IPython.core.pylabtools import figsize, getfigs
134 134
135 135 from pylab import *
136 136 from numpy import *
137 137
138 138 If you pass `--no-import-all`, the last two `*` imports will be excluded.
139 139
140 140 See the %matplotlib magic for more details about activating matplotlib
141 141 without affecting the interactive namespace.
142 142 """
143 143 args = magic_arguments.parse_argstring(self.pylab, line)
144 144 if args.no_import_all is None:
145 145 # get default from Application
146 146 if Application.initialized():
147 147 app = Application.instance()
148 148 try:
149 149 import_all = app.pylab_import_all
150 150 except AttributeError:
151 151 import_all = True
152 152 else:
153 153 # nothing specified, no app - default True
154 154 import_all = True
155 155 else:
156 156 # invert no-import flag
157 157 import_all = not args.no_import_all
158 158
159 159 gui, backend, clobbered = self.shell.enable_pylab(args.gui, import_all=import_all)
160 160 self._show_matplotlib_backend(args.gui, backend)
161 161 print(
162 162 "%pylab is deprecated, use %matplotlib inline and import the required libraries."
163 163 )
164 164 print("Populating the interactive namespace from numpy and matplotlib")
165 165 if clobbered:
166 166 warn("pylab import has clobbered these variables: %s" % clobbered +
167 167 "\n`%matplotlib` prevents importing * from pylab and numpy"
168 168 )
169 169
170 170 def _show_matplotlib_backend(self, gui, backend):
171 171 """show matplotlib message backend message"""
172 172 if not gui or gui == 'auto':
173 173 print("Using matplotlib backend: %s" % backend)
@@ -1,352 +1,376 b''
1 1 """Tests for pylab tools module.
2 2 """
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7
8 8 from binascii import a2b_base64
9 9 from io import BytesIO
10 10
11 11 import pytest
12 12
13 13 matplotlib = pytest.importorskip("matplotlib")
14 14 matplotlib.use('Agg')
15 15 from matplotlib.figure import Figure
16 16
17 17 from matplotlib import pyplot as plt
18 18 from matplotlib_inline import backend_inline
19 19 import numpy as np
20 20
21 21 from IPython.core.getipython import get_ipython
22 22 from IPython.core.interactiveshell import InteractiveShell
23 23 from IPython.core.display import _PNG, _JPEG
24 24 from .. import pylabtools as pt
25 25
26 26 from IPython.testing import decorators as dec
27 27
28 28
29 29 def test_figure_to_svg():
30 30 # simple empty-figure test
31 31 fig = plt.figure()
32 32 assert pt.print_figure(fig, "svg") is None
33 33
34 34 plt.close('all')
35 35
36 36 # simple check for at least svg-looking output
37 37 fig = plt.figure()
38 38 ax = fig.add_subplot(1,1,1)
39 39 ax.plot([1,2,3])
40 40 plt.draw()
41 41 svg = pt.print_figure(fig, "svg")[:100].lower()
42 42 assert "doctype svg" in svg
43 43
44 44
45 45 def _check_pil_jpeg_bytes():
46 46 """Skip if PIL can't write JPEGs to BytesIO objects"""
47 47 # PIL's JPEG plugin can't write to BytesIO objects
48 48 # Pillow fixes this
49 49 from PIL import Image
50 50 buf = BytesIO()
51 51 img = Image.new("RGB", (4,4))
52 52 try:
53 53 img.save(buf, 'jpeg')
54 54 except Exception as e:
55 55 ename = e.__class__.__name__
56 56 raise pytest.skip("PIL can't write JPEG to BytesIO: %s: %s" % (ename, e)) from e
57 57
58 58 @dec.skip_without("PIL.Image")
59 59 def test_figure_to_jpeg():
60 60 _check_pil_jpeg_bytes()
61 61 # simple check for at least jpeg-looking output
62 62 fig = plt.figure()
63 63 ax = fig.add_subplot(1,1,1)
64 64 ax.plot([1,2,3])
65 65 plt.draw()
66 66 jpeg = pt.print_figure(fig, 'jpeg', pil_kwargs={'optimize': 50})[:100].lower()
67 67 assert jpeg.startswith(_JPEG)
68 68
69 69 def test_retina_figure():
70 70 # simple empty-figure test
71 71 fig = plt.figure()
72 72 assert pt.retina_figure(fig) == None
73 73 plt.close('all')
74 74
75 75 fig = plt.figure()
76 76 ax = fig.add_subplot(1,1,1)
77 77 ax.plot([1,2,3])
78 78 plt.draw()
79 79 png, md = pt.retina_figure(fig)
80 80 assert png.startswith(_PNG)
81 81 assert "width" in md
82 82 assert "height" in md
83 83
84 84
85 85 _fmt_mime_map = {
86 86 'png': 'image/png',
87 87 'jpeg': 'image/jpeg',
88 88 'pdf': 'application/pdf',
89 89 'retina': 'image/png',
90 90 'svg': 'image/svg+xml',
91 91 }
92 92
93 93 def test_select_figure_formats_str():
94 94 ip = get_ipython()
95 95 for fmt, active_mime in _fmt_mime_map.items():
96 96 pt.select_figure_formats(ip, fmt)
97 97 for mime, f in ip.display_formatter.formatters.items():
98 98 if mime == active_mime:
99 99 assert Figure in f
100 100 else:
101 101 assert Figure not in f
102 102
103 103 def test_select_figure_formats_kwargs():
104 104 ip = get_ipython()
105 105 kwargs = dict(bbox_inches="tight")
106 106 pt.select_figure_formats(ip, "png", **kwargs)
107 107 formatter = ip.display_formatter.formatters["image/png"]
108 108 f = formatter.lookup_by_type(Figure)
109 109 cell = f.keywords
110 110 expected = kwargs
111 111 expected["base64"] = True
112 112 expected["fmt"] = "png"
113 113 assert cell == expected
114 114
115 115 # check that the formatter doesn't raise
116 116 fig = plt.figure()
117 117 ax = fig.add_subplot(1,1,1)
118 118 ax.plot([1,2,3])
119 119 plt.draw()
120 120 formatter.enabled = True
121 121 png = formatter(fig)
122 122 assert isinstance(png, str)
123 123 png_bytes = a2b_base64(png)
124 124 assert png_bytes.startswith(_PNG)
125 125
126 126 def test_select_figure_formats_set():
127 127 ip = get_ipython()
128 128 for fmts in [
129 129 {'png', 'svg'},
130 130 ['png'],
131 131 ('jpeg', 'pdf', 'retina'),
132 132 {'svg'},
133 133 ]:
134 134 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
135 135 pt.select_figure_formats(ip, fmts)
136 136 for mime, f in ip.display_formatter.formatters.items():
137 137 if mime in active_mimes:
138 138 assert Figure in f
139 139 else:
140 140 assert Figure not in f
141 141
142 142 def test_select_figure_formats_bad():
143 143 ip = get_ipython()
144 144 with pytest.raises(ValueError):
145 145 pt.select_figure_formats(ip, 'foo')
146 146 with pytest.raises(ValueError):
147 147 pt.select_figure_formats(ip, {'png', 'foo'})
148 148 with pytest.raises(ValueError):
149 149 pt.select_figure_formats(ip, ['retina', 'pdf', 'bar', 'bad'])
150 150
151 151 def test_import_pylab():
152 152 ns = {}
153 153 pt.import_pylab(ns, import_all=False)
154 154 assert "plt" in ns
155 155 assert ns["np"] == np
156 156
157 157
158 158 class TestPylabSwitch(object):
159 159 class Shell(InteractiveShell):
160 160 def init_history(self):
161 161 """Sets up the command history, and starts regular autosaves."""
162 162 self.config.HistoryManager.hist_file = ":memory:"
163 163 super().init_history()
164 164
165 165 def enable_gui(self, gui):
166 166 pass
167 167
168 168 def setup_method(self):
169 169 import matplotlib
170 170 def act_mpl(backend):
171 171 matplotlib.rcParams['backend'] = backend
172 172
173 173 # Save rcParams since they get modified
174 174 self._saved_rcParams = matplotlib.rcParams
175 175 self._saved_rcParamsOrig = matplotlib.rcParamsOrig
176 176 matplotlib.rcParams = dict(backend="QtAgg")
177 177 matplotlib.rcParamsOrig = dict(backend="QtAgg")
178 178
179 179 # Mock out functions
180 180 self._save_am = pt.activate_matplotlib
181 181 pt.activate_matplotlib = act_mpl
182 182 self._save_ip = pt.import_pylab
183 183 pt.import_pylab = lambda *a,**kw:None
184 184 self._save_cis = backend_inline.configure_inline_support
185 185 backend_inline.configure_inline_support = lambda *a, **kw: None
186 186
187 187 def teardown_method(self):
188 188 pt.activate_matplotlib = self._save_am
189 189 pt.import_pylab = self._save_ip
190 190 backend_inline.configure_inline_support = self._save_cis
191 191 import matplotlib
192 192 matplotlib.rcParams = self._saved_rcParams
193 193 matplotlib.rcParamsOrig = self._saved_rcParamsOrig
194 194
195 195 def test_qt(self):
196 196 s = self.Shell()
197 197 gui, backend = s.enable_matplotlib(None)
198 198 assert gui == "qt"
199 199 assert s.pylab_gui_select == "qt"
200 200
201 201 gui, backend = s.enable_matplotlib("inline")
202 202 assert gui is None
203 203 assert s.pylab_gui_select == "qt"
204 204
205 205 gui, backend = s.enable_matplotlib("qt")
206 206 assert gui == "qt"
207 207 assert s.pylab_gui_select == "qt"
208 208
209 209 gui, backend = s.enable_matplotlib("inline")
210 210 assert gui is None
211 211 assert s.pylab_gui_select == "qt"
212 212
213 213 gui, backend = s.enable_matplotlib()
214 214 assert gui == "qt"
215 215 assert s.pylab_gui_select == "qt"
216 216
217 217 def test_inline(self):
218 218 s = self.Shell()
219 219 gui, backend = s.enable_matplotlib("inline")
220 220 assert gui is None
221 221 assert s.pylab_gui_select == None
222 222
223 223 gui, backend = s.enable_matplotlib("inline")
224 224 assert gui is None
225 225 assert s.pylab_gui_select == None
226 226
227 227 gui, backend = s.enable_matplotlib("qt")
228 228 assert gui == "qt"
229 229 assert s.pylab_gui_select == "qt"
230 230
231 231 def test_inline_twice(self):
232 232 "Using '%matplotlib inline' twice should not reset formatters"
233 233
234 234 ip = self.Shell()
235 235 gui, backend = ip.enable_matplotlib("inline")
236 236 assert gui is None
237 237
238 238 fmts = {'png'}
239 239 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
240 240 pt.select_figure_formats(ip, fmts)
241 241
242 242 gui, backend = ip.enable_matplotlib("inline")
243 243 assert gui is None
244 244
245 245 for mime, f in ip.display_formatter.formatters.items():
246 246 if mime in active_mimes:
247 247 assert Figure in f
248 248 else:
249 249 assert Figure not in f
250 250
251 251 def test_qt_gtk(self):
252 252 s = self.Shell()
253 253 gui, backend = s.enable_matplotlib("qt")
254 254 assert gui == "qt"
255 255 assert s.pylab_gui_select == "qt"
256 256
257 257 gui, backend = s.enable_matplotlib("gtk3")
258 258 assert gui == "qt"
259 259 assert s.pylab_gui_select == "qt"
260 260
261 261
262 262 def test_no_gui_backends():
263 263 for k in ['agg', 'svg', 'pdf', 'ps']:
264 264 assert k not in pt.backend2gui
265 265
266 266
267 267 def test_figure_no_canvas():
268 268 fig = Figure()
269 269 fig.canvas = None
270 270 pt.print_figure(fig)
271 271
272 272
273 273 @pytest.mark.parametrize(
274 274 "name, expected_gui, expected_backend",
275 275 [
276 276 # name is gui
277 277 ("gtk3", "gtk3", "gtk3agg"),
278 278 ("gtk4", "gtk4", "gtk4agg"),
279 279 ("headless", None, "agg"),
280 280 ("osx", "osx", "macosx"),
281 281 ("qt", "qt", "qtagg"),
282 282 ("qt5", "qt5", "qt5agg"),
283 283 ("qt6", "qt6", "qtagg"),
284 284 ("tk", "tk", "tkagg"),
285 285 ("wx", "wx", "wxagg"),
286 286 # name is backend
287 287 ("agg", None, "agg"),
288 288 ("cairo", None, "cairo"),
289 289 ("pdf", None, "pdf"),
290 290 ("ps", None, "ps"),
291 291 ("svg", None, "svg"),
292 292 ("template", None, "template"),
293 293 ("gtk3agg", "gtk3", "gtk3agg"),
294 294 ("gtk3cairo", "gtk3", "gtk3cairo"),
295 295 ("gtk4agg", "gtk4", "gtk4agg"),
296 296 ("gtk4cairo", "gtk4", "gtk4cairo"),
297 297 ("macosx", "osx", "macosx"),
298 298 ("nbagg", "nbagg", "nbagg"),
299 299 ("notebook", "nbagg", "notebook"),
300 300 ("qtagg", "qt", "qtagg"),
301 301 ("qtcairo", "qt", "qtcairo"),
302 302 ("qt5agg", "qt5", "qt5agg"),
303 303 ("qt5cairo", "qt5", "qt5cairo"),
304 304 ("tkagg", "tk", "tkagg"),
305 305 ("tkcairo", "tk", "tkcairo"),
306 306 ("webagg", "webagg", "webagg"),
307 307 ("wxagg", "wx", "wxagg"),
308 308 ("wxcairo", "wx", "wxcairo"),
309 309 ],
310 310 )
311 311 def test_backend_builtin(name, expected_gui, expected_backend):
312 312 # Test correct identification of Matplotlib built-in backends without importing and using them,
313 313 # otherwise we would need to ensure all the complex dependencies such as windowing toolkits are
314 314 # installed.
315 315
316 316 mpl_manages_backends = pt._matplotlib_manages_backends()
317 317 if not mpl_manages_backends:
318 318 # Backends not supported before _matplotlib_manages_backends or supported
319 319 # but with different expected_gui or expected_backend.
320 320 if (
321 321 name.endswith("agg")
322 322 or name.endswith("cairo")
323 323 or name in ("headless", "macosx", "pdf", "ps", "svg", "template")
324 324 ):
325 325 pytest.skip()
326 326 elif name == "qt6":
327 327 expected_backend = "qtagg"
328 328 elif name == "notebook":
329 329 expected_backend, expected_gui = expected_gui, expected_backend
330 330
331 331 gui, backend = pt.find_gui_and_backend(name)
332 332 if not mpl_manages_backends:
333 333 gui = gui.lower() if gui else None
334 334 backend = backend.lower() if backend else None
335 335 assert gui == expected_gui
336 336 assert backend == expected_backend
337 337
338 338
339 339 def test_backend_entry_point():
340 340 gui, backend = pt.find_gui_and_backend("inline")
341 341 assert gui is None
342 342 expected_backend = (
343 343 "inline"
344 344 if pt._matplotlib_manages_backends()
345 345 else "module://matplotlib_inline.backend_inline"
346 346 )
347 347 assert backend == expected_backend
348 348
349 349
350 350 def test_backend_unknown():
351 351 with pytest.raises(RuntimeError if pt._matplotlib_manages_backends() else KeyError):
352 352 pt.find_gui_and_backend("name-does-not-exist")
353
354
355 @dec.skipif(not pt._matplotlib_manages_backends())
356 def test_backend_module_name_case_sensitive():
357 # Matplotlib backend names are case insensitive unless explicitly specified using
358 # "module://some_module.some_name" syntax which are case sensitive for mpl >= 3.9.1
359 all_lowercase = "module://matplotlib_inline.backend_inline"
360 some_uppercase = "module://matplotlib_inline.Backend_inline"
361 ip = get_ipython()
362 mpl3_9_1 = matplotlib.__version_info__ >= (3, 9, 1)
363
364 ip.enable_matplotlib(all_lowercase)
365 if mpl3_9_1:
366 with pytest.raises(RuntimeError):
367 ip.enable_matplotlib(some_uppercase)
368 else:
369 ip.enable_matplotlib(some_uppercase)
370
371 ip.run_line_magic("matplotlib", all_lowercase)
372 if mpl3_9_1:
373 with pytest.raises(RuntimeError):
374 ip.run_line_magic("matplotlib", some_uppercase)
375 else:
376 ip.run_line_magic("matplotlib", some_uppercase)
General Comments 0
You need to be logged in to leave comments. Login now