##// END OF EJS Templates
Move Matplotlib backend resolution to Matplotlib
Ian Thomas -
Show More
@@ -3657,7 +3657,7 b' class InteractiveShell(SingletonConfigurable):'
3657 from IPython.core import pylabtools as pt
3657 from IPython.core import pylabtools as pt
3658 gui, backend = pt.find_gui_and_backend(gui, self.pylab_gui_select)
3658 gui, backend = pt.find_gui_and_backend(gui, self.pylab_gui_select)
3659
3659
3660 if gui != 'inline':
3660 if gui != None:
3661 # If we have our first gui selection, store it
3661 # If we have our first gui selection, store it
3662 if self.pylab_gui_select is None:
3662 if self.pylab_gui_select is None:
3663 self.pylab_gui_select = gui
3663 self.pylab_gui_select = gui
@@ -18,7 +18,6 b' from IPython.core import magic_arguments'
18 from IPython.core.magic import Magics, magics_class, line_magic
18 from IPython.core.magic import Magics, magics_class, line_magic
19 from IPython.testing.skipdoctest import skip_doctest
19 from IPython.testing.skipdoctest import skip_doctest
20 from warnings import warn
20 from warnings import warn
21 from IPython.core.pylabtools import backends
22
21
23 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
24 # Magic implementation classes
23 # Magic implementation classes
@@ -26,11 +25,11 b' from IPython.core.pylabtools import backends'
26
25
27 magic_gui_arg = magic_arguments.argument(
26 magic_gui_arg = magic_arguments.argument(
28 'gui', nargs='?',
27 'gui', nargs='?',
29 help="""Name of the matplotlib backend to use %s.
28 help="""Name of the matplotlib backend to use such as 'qt' or 'widget'.
30 If given, the corresponding matplotlib backend is used,
29 If given, the corresponding matplotlib backend is used,
31 otherwise it will be matplotlib's default
30 otherwise it will be matplotlib's default
32 (which you can set in your matplotlib config file).
31 (which you can set in your matplotlib config file).
33 """ % str(tuple(sorted(backends.keys())))
32 """
34 )
33 )
35
34
36
35
@@ -93,7 +92,13 b' class PylabMagics(Magics):'
93 """
92 """
94 args = magic_arguments.parse_argstring(self.matplotlib, line)
93 args = magic_arguments.parse_argstring(self.matplotlib, line)
95 if args.list:
94 if args.list:
96 backends_list = list(backends.keys())
95 from IPython.core.pylabtools import _matplotlib_manages_backends
96 if _matplotlib_manages_backends():
97 from matplotlib.backends.registry import backend_registry
98 backends_list = backend_registry.list_all()
99 else:
100 from IPython.core.pylabtools import backends
101 backends_list = list(backends.keys())
97 print("Available matplotlib backends: %s" % backends_list)
102 print("Available matplotlib backends: %s" % backends_list)
98 else:
103 else:
99 gui, backend = self.shell.enable_matplotlib(args.gui.lower() if isinstance(args.gui, str) else args.gui)
104 gui, backend = self.shell.enable_matplotlib(args.gui.lower() if isinstance(args.gui, str) else args.gui)
@@ -12,9 +12,12 b' import warnings'
12 from IPython.core.display import _pngxy
12 from IPython.core.display import _pngxy
13 from IPython.utils.decorators import flag_calls
13 from IPython.utils.decorators import flag_calls
14
14
15 # If user specifies a GUI, that dictates the backend, otherwise we read the
15
16 # user's mpl default from the mpl rc structure
16 # Matplotlib backend resolution functionality moved from IPython to Matplotlib
17 backends = {
17 # in IPython 8.23 and Matplotlib 3.9. Need to keep `backends` and `backend2gui`
18 # here for earlier Matplotlib and for external backend libraries such as
19 # mplcairo that might rely upon it.
20 _deprecated_backends = {
18 "tk": "TkAgg",
21 "tk": "TkAgg",
19 "gtk": "GTKAgg",
22 "gtk": "GTKAgg",
20 "gtk3": "GTK3Agg",
23 "gtk3": "GTK3Agg",
@@ -41,29 +44,38 b' backends = {'
41 # GUI support to activate based on the desired matplotlib backend. For the
44 # GUI support to activate based on the desired matplotlib backend. For the
42 # most part it's just a reverse of the above dict, but we also need to add a
45 # most part it's just a reverse of the above dict, but we also need to add a
43 # few others that map to the same GUI manually:
46 # few others that map to the same GUI manually:
44 backend2gui = dict(zip(backends.values(), backends.keys()))
47 _deprecated_backend2gui = dict(zip(_deprecated_backends.values(), _deprecated_backends.keys()))
45 # In the reverse mapping, there are a few extra valid matplotlib backends that
48 # In the reverse mapping, there are a few extra valid matplotlib backends that
46 # map to the same GUI support
49 # map to the same GUI support
47 backend2gui["GTK"] = backend2gui["GTKCairo"] = "gtk"
50 _deprecated_backend2gui["GTK"] = _deprecated_backend2gui["GTKCairo"] = "gtk"
48 backend2gui["GTK3Cairo"] = "gtk3"
51 _deprecated_backend2gui["GTK3Cairo"] = "gtk3"
49 backend2gui["GTK4Cairo"] = "gtk4"
52 _deprecated_backend2gui["GTK4Cairo"] = "gtk4"
50 backend2gui["WX"] = "wx"
53 _deprecated_backend2gui["WX"] = "wx"
51 backend2gui["CocoaAgg"] = "osx"
54 _deprecated_backend2gui["CocoaAgg"] = "osx"
52 # There needs to be a hysteresis here as the new QtAgg Matplotlib backend
55 # There needs to be a hysteresis here as the new QtAgg Matplotlib backend
53 # supports either Qt5 or Qt6 and the IPython qt event loop support Qt4, Qt5,
56 # supports either Qt5 or Qt6 and the IPython qt event loop support Qt4, Qt5,
54 # and Qt6.
57 # and Qt6.
55 backend2gui["QtAgg"] = "qt"
58 _deprecated_backend2gui["QtAgg"] = "qt"
56 backend2gui["Qt4Agg"] = "qt4"
59 _deprecated_backend2gui["Qt4Agg"] = "qt4"
57 backend2gui["Qt5Agg"] = "qt5"
60 _deprecated_backend2gui["Qt5Agg"] = "qt5"
58
61
59 # And some backends that don't need GUI integration
62 # And some backends that don't need GUI integration
60 del backend2gui["nbAgg"]
63 del _deprecated_backend2gui["nbAgg"]
61 del backend2gui["agg"]
64 del _deprecated_backend2gui["agg"]
62 del backend2gui["svg"]
65 del _deprecated_backend2gui["svg"]
63 del backend2gui["pdf"]
66 del _deprecated_backend2gui["pdf"]
64 del backend2gui["ps"]
67 del _deprecated_backend2gui["ps"]
65 del backend2gui["module://matplotlib_inline.backend_inline"]
68 del _deprecated_backend2gui["module://matplotlib_inline.backend_inline"]
66 del backend2gui["module://ipympl.backend_nbagg"]
69 del _deprecated_backend2gui["module://ipympl.backend_nbagg"]
70
71
72 # Deprecated attributes backends and backend2gui mostly following PEP 562.
73 def __getattr__(name):
74 if name in ("backends", "backend2gui"):
75 warnings.warn(f"{name} is deprecated", DeprecationWarning)
76 return globals()[f"_deprecated_{name}"]
77 raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
78
67
79
68 #-----------------------------------------------------------------------------
80 #-----------------------------------------------------------------------------
69 # Matplotlib utilities
81 # Matplotlib utilities
@@ -267,7 +279,7 b' def select_figure_formats(shell, formats, **kwargs):'
267
279
268 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
280 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
269 mplbackend = matplotlib.get_backend().lower()
281 mplbackend = matplotlib.get_backend().lower()
270 if mplbackend == 'nbagg' or mplbackend == 'module://ipympl.backend_nbagg':
282 if mplbackend in ('nbagg', 'ipympl', 'widget', 'module://ipympl.backend_nbagg'):
271 formatter = shell.display_formatter.ipython_display_formatter
283 formatter = shell.display_formatter.ipython_display_formatter
272 formatter.for_type(Figure, _reshow_nbagg_figure)
284 formatter.for_type(Figure, _reshow_nbagg_figure)
273
285
@@ -318,9 +330,23 b' def find_gui_and_backend(gui=None, gui_select=None):'
318 """
330 """
319
331
320 import matplotlib
332 import matplotlib
333 if _matplotlib_manages_backends():
334 backend_registry = matplotlib.backends.registry.backend_registry
335
336 # gui argument may be a gui event loop or may be a backend name.
337 if gui in ("auto", None):
338 backend = matplotlib.rcParamsOrig['backend']
339 backend, gui = backend_registry.resolve_backend(backend)
340 else:
341 backend, gui = backend_registry.resolve_gui_or_backend(gui)
321
342
322 has_unified_qt_backend = getattr(matplotlib, "__version_info__", (0, 0)) >= (3, 5)
343 return gui, backend
323
344
345 # Fallback to previous behaviour (Matplotlib < 3.9)
346 mpl_version_info = getattr(matplotlib, "__version_info__", (0, 0))
347 has_unified_qt_backend = mpl_version_info >= (3, 5)
348
349 from IPython.core.pylabtools import backends
324 backends_ = dict(backends)
350 backends_ = dict(backends)
325 if not has_unified_qt_backend:
351 if not has_unified_qt_backend:
326 backends_["qt"] = "qt5agg"
352 backends_["qt"] = "qt5agg"
@@ -338,6 +364,7 b' def find_gui_and_backend(gui=None, gui_select=None):'
338 backend = matplotlib.rcParamsOrig['backend']
364 backend = matplotlib.rcParamsOrig['backend']
339 # In this case, we need to find what the appropriate gui selection call
365 # In this case, we need to find what the appropriate gui selection call
340 # should be for IPython, so we can activate inputhook accordingly
366 # should be for IPython, so we can activate inputhook accordingly
367 from IPython.core.pylabtools import backend2gui
341 gui = backend2gui.get(backend, None)
368 gui = backend2gui.get(backend, None)
342
369
343 # If we have already had a gui active, we need it and inline are the
370 # If we have already had a gui active, we need it and inline are the
@@ -346,6 +373,10 b' def find_gui_and_backend(gui=None, gui_select=None):'
346 gui = gui_select
373 gui = gui_select
347 backend = backends_[gui]
374 backend = backends_[gui]
348
375
376 # Since IPython 8.23.0 use None for no gui event loop rather than "inline".
377 if gui == "inline":
378 gui = None
379
349 return gui, backend
380 return gui, backend
350
381
351
382
@@ -431,3 +462,9 b' def configure_inline_support(shell, backend):'
431 )
462 )
432
463
433 configure_inline_support_orig(shell, backend)
464 configure_inline_support_orig(shell, backend)
465
466
467 def _matplotlib_manages_backends():
468 import matplotlib
469 mpl_version_info = getattr(matplotlib, "__version_info__", (0, 0))
470 return mpl_version_info >= (3, 9)
@@ -31,8 +31,7 b' from IPython.terminal import pt_inputhooks'
31
31
32 gui_keys = tuple(sorted(pt_inputhooks.backends) + sorted(pt_inputhooks.aliases))
32 gui_keys = tuple(sorted(pt_inputhooks.backends) + sorted(pt_inputhooks.aliases))
33
33
34 backend_keys = sorted(pylabtools.backends.keys())
34 backend_keys = []
35 backend_keys.insert(0, 'auto')
36
35
37 shell_flags = {}
36 shell_flags = {}
38
37
@@ -199,7 +199,7 b' class TestPylabSwitch(object):'
199 assert s.pylab_gui_select == "qt"
199 assert s.pylab_gui_select == "qt"
200
200
201 gui, backend = s.enable_matplotlib("inline")
201 gui, backend = s.enable_matplotlib("inline")
202 assert gui == "inline"
202 assert gui is None
203 assert s.pylab_gui_select == "qt"
203 assert s.pylab_gui_select == "qt"
204
204
205 gui, backend = s.enable_matplotlib("qt")
205 gui, backend = s.enable_matplotlib("qt")
@@ -207,7 +207,7 b' class TestPylabSwitch(object):'
207 assert s.pylab_gui_select == "qt"
207 assert s.pylab_gui_select == "qt"
208
208
209 gui, backend = s.enable_matplotlib("inline")
209 gui, backend = s.enable_matplotlib("inline")
210 assert gui == "inline"
210 assert gui is None
211 assert s.pylab_gui_select == "qt"
211 assert s.pylab_gui_select == "qt"
212
212
213 gui, backend = s.enable_matplotlib()
213 gui, backend = s.enable_matplotlib()
@@ -217,11 +217,11 b' class TestPylabSwitch(object):'
217 def test_inline(self):
217 def test_inline(self):
218 s = self.Shell()
218 s = self.Shell()
219 gui, backend = s.enable_matplotlib("inline")
219 gui, backend = s.enable_matplotlib("inline")
220 assert gui == "inline"
220 assert gui is None
221 assert s.pylab_gui_select == None
221 assert s.pylab_gui_select == None
222
222
223 gui, backend = s.enable_matplotlib("inline")
223 gui, backend = s.enable_matplotlib("inline")
224 assert gui == "inline"
224 assert gui is None
225 assert s.pylab_gui_select == None
225 assert s.pylab_gui_select == None
226
226
227 gui, backend = s.enable_matplotlib("qt")
227 gui, backend = s.enable_matplotlib("qt")
@@ -233,14 +233,14 b' class TestPylabSwitch(object):'
233
233
234 ip = self.Shell()
234 ip = self.Shell()
235 gui, backend = ip.enable_matplotlib("inline")
235 gui, backend = ip.enable_matplotlib("inline")
236 assert gui == "inline"
236 assert gui is None
237
237
238 fmts = {'png'}
238 fmts = {'png'}
239 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
239 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
240 pt.select_figure_formats(ip, fmts)
240 pt.select_figure_formats(ip, fmts)
241
241
242 gui, backend = ip.enable_matplotlib("inline")
242 gui, backend = ip.enable_matplotlib("inline")
243 assert gui == "inline"
243 assert gui is None
244
244
245 for mime, f in ip.display_formatter.formatters.items():
245 for mime, f in ip.display_formatter.formatters.items():
246 if mime in active_mimes:
246 if mime in active_mimes:
@@ -254,7 +254,7 b' class TestPylabSwitch(object):'
254 assert gui == "qt"
254 assert gui == "qt"
255 assert s.pylab_gui_select == "qt"
255 assert s.pylab_gui_select == "qt"
256
256
257 gui, backend = s.enable_matplotlib("gtk")
257 gui, backend = s.enable_matplotlib("gtk3")
258 assert gui == "qt"
258 assert gui == "qt"
259 assert s.pylab_gui_select == "qt"
259 assert s.pylab_gui_select == "qt"
260
260
@@ -966,7 +966,7 b' class TerminalInteractiveShell(InteractiveShell):'
966 if self._inputhook is not None and gui is None:
966 if self._inputhook is not None and gui is None:
967 self.active_eventloop = self._inputhook = None
967 self.active_eventloop = self._inputhook = None
968
968
969 if gui and (gui not in {"inline", "webagg"}):
969 if gui and (gui not in {None, "webagg"}):
970 # This hook runs with each cycle of the `prompt_toolkit`'s event loop.
970 # This hook runs with each cycle of the `prompt_toolkit`'s event loop.
971 self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui)
971 self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui)
972 else:
972 else:
General Comments 0
You need to be logged in to leave comments. Login now