##// END OF EJS Templates
Move Matplotlib backend mapping to Matplotlib (#14371)...
M Bussonnier -
r28726:e0d3e4cd merge
parent child Browse files
Show More
@@ -49,6 +49,11 b' jobs:'
49 - os: macos-latest
49 - os: macos-latest
50 python-version: "pypy-3.10"
50 python-version: "pypy-3.10"
51 deps: test
51 deps: test
52 # Temporary CI run to use entry point compatible code in matplotlib-inline.
53 - os: ubuntu-latest
54 python-version: "3.12"
55 deps: test_extra
56 want-latest-entry-point-code: true
52
57
53 steps:
58 steps:
54 - uses: actions/checkout@v3
59 - uses: actions/checkout@v3
@@ -82,6 +87,16 b' jobs:'
82 - name: Check manifest
87 - name: Check manifest
83 if: runner.os != 'Windows' # setup.py does not support sdist on Windows
88 if: runner.os != 'Windows' # setup.py does not support sdist on Windows
84 run: check-manifest
89 run: check-manifest
90
91 - name: Install entry point compatible code (TEMPORARY)
92 if: matrix.want-latest-entry-point-code
93 run: |
94 python -m pip list
95 # Not installing matplotlib's entry point code as building matplotlib from source is complex.
96 # Rely upon matplotlib to test all the latest entry point branches together.
97 python -m pip install --upgrade git+https://github.com/ipython/matplotlib-inline.git@main
98 python -m pip list
99
85 - name: pytest
100 - name: pytest
86 env:
101 env:
87 COLUMNS: 120
102 COLUMNS: 120
@@ -111,7 +111,7 b' def display('
111 display_id=None,
111 display_id=None,
112 raw=False,
112 raw=False,
113 clear=False,
113 clear=False,
114 **kwargs
114 **kwargs,
115 ):
115 ):
116 """Display a Python object in all frontends.
116 """Display a Python object in all frontends.
117
117
@@ -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,19 +18,19 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
25 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
26
25
27 magic_gui_arg = magic_arguments.argument(
26 magic_gui_arg = magic_arguments.argument(
28 'gui', nargs='?',
27 "gui",
29 help="""Name of the matplotlib backend to use %s.
28 nargs="?",
29 help="""Name of the matplotlib backend to use such as 'qt' or 'widget'.
30 If given, the corresponding matplotlib backend is used,
30 If given, the corresponding matplotlib backend is used,
31 otherwise it will be matplotlib's default
31 otherwise it will be matplotlib's default
32 (which you can set in your matplotlib config file).
32 (which you can set in your matplotlib config file).
33 """ % str(tuple(sorted(backends.keys())))
33 """,
34 )
34 )
35
35
36
36
@@ -93,8 +93,12 b' class PylabMagics(Magics):'
93 """
93 """
94 args = magic_arguments.parse_argstring(self.matplotlib, line)
94 args = magic_arguments.parse_argstring(self.matplotlib, line)
95 if args.list:
95 if args.list:
96 backends_list = list(backends.keys())
96 from IPython.core.pylabtools import _list_matplotlib_backends_and_gui_loops
97 print("Available matplotlib backends: %s" % backends_list)
97
98 print(
99 "Available matplotlib backends: %s"
100 % _list_matplotlib_backends_and_gui_loops()
101 )
98 else:
102 else:
99 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.lower() if isinstance(args.gui, str) else args.gui)
100 self._show_matplotlib_backend(args.gui, backend)
104 self._show_matplotlib_backend(args.gui, backend)
@@ -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.24 and Matplotlib 3.9.1. 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,44 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(
48 zip(_deprecated_backends.values(), _deprecated_backends.keys())
49 )
45 # In the reverse mapping, there are a few extra valid matplotlib backends that
50 # In the reverse mapping, there are a few extra valid matplotlib backends that
46 # map to the same GUI support
51 # map to the same GUI support
47 backend2gui["GTK"] = backend2gui["GTKCairo"] = "gtk"
52 _deprecated_backend2gui["GTK"] = _deprecated_backend2gui["GTKCairo"] = "gtk"
48 backend2gui["GTK3Cairo"] = "gtk3"
53 _deprecated_backend2gui["GTK3Cairo"] = "gtk3"
49 backend2gui["GTK4Cairo"] = "gtk4"
54 _deprecated_backend2gui["GTK4Cairo"] = "gtk4"
50 backend2gui["WX"] = "wx"
55 _deprecated_backend2gui["WX"] = "wx"
51 backend2gui["CocoaAgg"] = "osx"
56 _deprecated_backend2gui["CocoaAgg"] = "osx"
52 # There needs to be a hysteresis here as the new QtAgg Matplotlib backend
57 # 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,
58 # supports either Qt5 or Qt6 and the IPython qt event loop support Qt4, Qt5,
54 # and Qt6.
59 # and Qt6.
55 backend2gui["QtAgg"] = "qt"
60 _deprecated_backend2gui["QtAgg"] = "qt"
56 backend2gui["Qt4Agg"] = "qt4"
61 _deprecated_backend2gui["Qt4Agg"] = "qt4"
57 backend2gui["Qt5Agg"] = "qt5"
62 _deprecated_backend2gui["Qt5Agg"] = "qt5"
58
63
59 # And some backends that don't need GUI integration
64 # And some backends that don't need GUI integration
60 del backend2gui["nbAgg"]
65 del _deprecated_backend2gui["nbAgg"]
61 del backend2gui["agg"]
66 del _deprecated_backend2gui["agg"]
62 del backend2gui["svg"]
67 del _deprecated_backend2gui["svg"]
63 del backend2gui["pdf"]
68 del _deprecated_backend2gui["pdf"]
64 del backend2gui["ps"]
69 del _deprecated_backend2gui["ps"]
65 del backend2gui["module://matplotlib_inline.backend_inline"]
70 del _deprecated_backend2gui["module://matplotlib_inline.backend_inline"]
66 del backend2gui["module://ipympl.backend_nbagg"]
71 del _deprecated_backend2gui["module://ipympl.backend_nbagg"]
72
73
74 # Deprecated attributes backends and backend2gui mostly following PEP 562.
75 def __getattr__(name):
76 if name in ("backends", "backend2gui"):
77 warnings.warn(
78 f"{name} is deprecated since IPython 8.24, backends are managed "
79 "in matplotlib and can be externally registered.",
80 DeprecationWarning,
81 )
82 return globals()[f"_deprecated_{name}"]
83 raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
84
67
85
68 #-----------------------------------------------------------------------------
86 #-----------------------------------------------------------------------------
69 # Matplotlib utilities
87 # Matplotlib utilities
@@ -267,7 +285,7 b' def select_figure_formats(shell, formats, **kwargs):'
267
285
268 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
286 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
269 mplbackend = matplotlib.get_backend().lower()
287 mplbackend = matplotlib.get_backend().lower()
270 if mplbackend == 'nbagg' or mplbackend == 'module://ipympl.backend_nbagg':
288 if mplbackend in ("nbagg", "ipympl", "widget", "module://ipympl.backend_nbagg"):
271 formatter = shell.display_formatter.ipython_display_formatter
289 formatter = shell.display_formatter.ipython_display_formatter
272 formatter.for_type(Figure, _reshow_nbagg_figure)
290 formatter.for_type(Figure, _reshow_nbagg_figure)
273
291
@@ -319,7 +337,23 b' def find_gui_and_backend(gui=None, gui_select=None):'
319
337
320 import matplotlib
338 import matplotlib
321
339
322 has_unified_qt_backend = getattr(matplotlib, "__version_info__", (0, 0)) >= (3, 5)
340 if _matplotlib_manages_backends():
341 backend_registry = matplotlib.backends.registry.backend_registry
342
343 # gui argument may be a gui event loop or may be a backend name.
344 if gui in ("auto", None):
345 backend = matplotlib.rcParamsOrig["backend"]
346 backend, gui = backend_registry.resolve_backend(backend)
347 else:
348 backend, gui = backend_registry.resolve_gui_or_backend(gui)
349
350 return gui, backend
351
352 # Fallback to previous behaviour (Matplotlib < 3.9)
353 mpl_version_info = getattr(matplotlib, "__version_info__", (0, 0))
354 has_unified_qt_backend = mpl_version_info >= (3, 5)
355
356 from IPython.core.pylabtools import backends
323
357
324 backends_ = dict(backends)
358 backends_ = dict(backends)
325 if not has_unified_qt_backend:
359 if not has_unified_qt_backend:
@@ -338,6 +372,7 b' def find_gui_and_backend(gui=None, gui_select=None):'
338 backend = matplotlib.rcParamsOrig['backend']
372 backend = matplotlib.rcParamsOrig['backend']
339 # In this case, we need to find what the appropriate gui selection call
373 # In this case, we need to find what the appropriate gui selection call
340 # should be for IPython, so we can activate inputhook accordingly
374 # should be for IPython, so we can activate inputhook accordingly
375 from IPython.core.pylabtools import backend2gui
341 gui = backend2gui.get(backend, None)
376 gui = backend2gui.get(backend, None)
342
377
343 # If we have already had a gui active, we need it and inline are the
378 # If we have already had a gui active, we need it and inline are the
@@ -346,6 +381,11 b' def find_gui_and_backend(gui=None, gui_select=None):'
346 gui = gui_select
381 gui = gui_select
347 backend = backends_[gui]
382 backend = backends_[gui]
348
383
384 # Matplotlib before _matplotlib_manages_backends() can return "inline" for
385 # no gui event loop rather than the None that IPython >= 8.24.0 expects.
386 if gui == "inline":
387 gui = None
388
349 return gui, backend
389 return gui, backend
350
390
351
391
@@ -431,3 +471,48 b' def configure_inline_support(shell, backend):'
431 )
471 )
432
472
433 configure_inline_support_orig(shell, backend)
473 configure_inline_support_orig(shell, backend)
474
475
476 # Determine if Matplotlib manages backends only if needed, and cache result.
477 # Do not read this directly, instead use _matplotlib_manages_backends().
478 _matplotlib_manages_backends_value: bool | None = None
479
480
481 def _matplotlib_manages_backends() -> bool:
482 """Return True if Matplotlib manages backends, False otherwise.
483
484 If it returns True, the caller can be sure that
485 matplotlib.backends.registry.backend_registry is available along with
486 member functions resolve_gui_or_backend, resolve_backend, list_all, and
487 list_gui_frameworks.
488 """
489 global _matplotlib_manages_backends_value
490 if _matplotlib_manages_backends_value is None:
491 try:
492 from matplotlib.backends.registry import backend_registry
493
494 _matplotlib_manages_backends_value = hasattr(
495 backend_registry, "resolve_gui_or_backend"
496 )
497 except ImportError:
498 _matplotlib_manages_backends_value = False
499
500 return _matplotlib_manages_backends_value
501
502
503 def _list_matplotlib_backends_and_gui_loops() -> list[str]:
504 """Return list of all Matplotlib backends and GUI event loops.
505
506 This is the list returned by
507 %matplotlib --list
508 """
509 if _matplotlib_manages_backends():
510 from matplotlib.backends.registry import backend_registry
511
512 ret = backend_registry.list_all() + backend_registry.list_gui_frameworks()
513 else:
514 from IPython.core import pylabtools
515
516 ret = list(pylabtools.backends.keys())
517
518 return sorted(["auto"] + ret)
@@ -11,37 +11,45 b' import glob'
11 from itertools import chain
11 from itertools import chain
12 import os
12 import os
13 import sys
13 import sys
14 import typing as t
14
15
15 from traitlets.config.application import boolean_flag
16 from traitlets.config.application import boolean_flag
16 from traitlets.config.configurable import Configurable
17 from traitlets.config.configurable import Configurable
17 from traitlets.config.loader import Config
18 from traitlets.config.loader import Config
18 from IPython.core.application import SYSTEM_CONFIG_DIRS, ENV_CONFIG_DIRS
19 from IPython.core.application import SYSTEM_CONFIG_DIRS, ENV_CONFIG_DIRS
19 from IPython.core import pylabtools
20 from IPython.utils.contexts import preserve_keys
20 from IPython.utils.contexts import preserve_keys
21 from IPython.utils.path import filefind
21 from IPython.utils.path import filefind
22 from traitlets import (
22 from traitlets import (
23 Unicode, Instance, List, Bool, CaselessStrEnum, observe,
23 Unicode,
24 Instance,
25 List,
26 Bool,
27 CaselessStrEnum,
28 observe,
24 DottedObjectName,
29 DottedObjectName,
30 Undefined,
25 )
31 )
26 from IPython.terminal import pt_inputhooks
32 from IPython.terminal import pt_inputhooks
27
33
28 #-----------------------------------------------------------------------------
34 # -----------------------------------------------------------------------------
29 # Aliases and Flags
35 # Aliases and Flags
30 #-----------------------------------------------------------------------------
36 # -----------------------------------------------------------------------------
31
37
32 gui_keys = tuple(sorted(pt_inputhooks.backends) + sorted(pt_inputhooks.aliases))
38 gui_keys = tuple(sorted(pt_inputhooks.backends) + sorted(pt_inputhooks.aliases))
33
39
34 backend_keys = sorted(pylabtools.backends.keys())
35 backend_keys.insert(0, 'auto')
36
37 shell_flags = {}
40 shell_flags = {}
38
41
39 addflag = lambda *args: shell_flags.update(boolean_flag(*args))
42 addflag = lambda *args: shell_flags.update(boolean_flag(*args))
40 addflag('autoindent', 'InteractiveShell.autoindent',
43 addflag(
41 'Turn on autoindenting.', 'Turn off autoindenting.'
44 "autoindent",
45 "InteractiveShell.autoindent",
46 "Turn on autoindenting.",
47 "Turn off autoindenting.",
42 )
48 )
43 addflag('automagic', 'InteractiveShell.automagic',
49 addflag(
44 """Turn on the auto calling of magic commands. Type %%magic at the
50 "automagic",
51 "InteractiveShell.automagic",
52 """Turn on the auto calling of magic commands. Type %%magic at the
45 IPython prompt for more information.""",
53 IPython prompt for more information.""",
46 'Turn off the auto calling of magic commands.'
54 'Turn off the auto calling of magic commands.'
47 )
55 )
@@ -97,6 +105,37 b' shell_aliases = dict('
97 )
105 )
98 shell_aliases['cache-size'] = 'InteractiveShell.cache_size'
106 shell_aliases['cache-size'] = 'InteractiveShell.cache_size'
99
107
108
109 # -----------------------------------------------------------------------------
110 # Traitlets
111 # -----------------------------------------------------------------------------
112
113
114 class MatplotlibBackendCaselessStrEnum(CaselessStrEnum):
115 """An enum of Matplotlib backend strings where the case should be ignored.
116
117 Prior to Matplotlib 3.9.1 the list of valid backends is hardcoded in
118 pylabtools.backends. After that, Matplotlib manages backends.
119
120 The list of valid backends is determined when it is first needed to avoid
121 wasting unnecessary initialisation time.
122 """
123
124 def __init__(
125 self: CaselessStrEnum[t.Any],
126 default_value: t.Any = Undefined,
127 **kwargs: t.Any,
128 ) -> None:
129 super().__init__(None, default_value=default_value, **kwargs)
130
131 def __getattribute__(self, name):
132 if name == "values" and object.__getattribute__(self, name) is None:
133 from IPython.core.pylabtools import _list_matplotlib_backends_and_gui_loops
134
135 self.values = _list_matplotlib_backends_and_gui_loops()
136 return object.__getattribute__(self, name)
137
138
100 #-----------------------------------------------------------------------------
139 #-----------------------------------------------------------------------------
101 # Main classes and functions
140 # Main classes and functions
102 #-----------------------------------------------------------------------------
141 #-----------------------------------------------------------------------------
@@ -156,30 +195,31 b' class InteractiveShellApp(Configurable):'
156 exec_lines = List(Unicode(),
195 exec_lines = List(Unicode(),
157 help="""lines of code to run at IPython startup."""
196 help="""lines of code to run at IPython startup."""
158 ).tag(config=True)
197 ).tag(config=True)
159 code_to_run = Unicode('',
198 code_to_run = Unicode("", help="Execute the given command string.").tag(config=True)
160 help="Execute the given command string."
199 module_to_run = Unicode("", help="Run the module as a script.").tag(config=True)
161 ).tag(config=True)
200 gui = CaselessStrEnum(
162 module_to_run = Unicode('',
201 gui_keys,
163 help="Run the module as a script."
202 allow_none=True,
203 help="Enable GUI event loop integration with any of {0}.".format(gui_keys),
164 ).tag(config=True)
204 ).tag(config=True)
165 gui = CaselessStrEnum(gui_keys, allow_none=True,
205 matplotlib = MatplotlibBackendCaselessStrEnum(
166 help="Enable GUI event loop integration with any of {0}.".format(gui_keys)
206 allow_none=True,
167 ).tag(config=True)
168 matplotlib = CaselessStrEnum(backend_keys, allow_none=True,
169 help="""Configure matplotlib for interactive use with
207 help="""Configure matplotlib for interactive use with
170 the default matplotlib backend."""
208 the default matplotlib backend.""",
171 ).tag(config=True)
209 ).tag(config=True)
172 pylab = CaselessStrEnum(backend_keys, allow_none=True,
210 pylab = MatplotlibBackendCaselessStrEnum(
211 allow_none=True,
173 help="""Pre-load matplotlib and numpy for interactive use,
212 help="""Pre-load matplotlib and numpy for interactive use,
174 selecting a particular matplotlib backend and loop integration.
213 selecting a particular matplotlib backend and loop integration.
175 """
214 """,
176 ).tag(config=True)
215 ).tag(config=True)
177 pylab_import_all = Bool(True,
216 pylab_import_all = Bool(
217 True,
178 help="""If true, IPython will populate the user namespace with numpy, pylab, etc.
218 help="""If true, IPython will populate the user namespace with numpy, pylab, etc.
179 and an ``import *`` is done from numpy and pylab, when using pylab mode.
219 and an ``import *`` is done from numpy and pylab, when using pylab mode.
180
220
181 When False, pylab mode should not import any names into the user namespace.
221 When False, pylab mode should not import any names into the user namespace.
182 """
222 """,
183 ).tag(config=True)
223 ).tag(config=True)
184 ignore_cwd = Bool(
224 ignore_cwd = Bool(
185 False,
225 False,
@@ -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
@@ -268,3 +268,87 b' def test_figure_no_canvas():'
268 fig = Figure()
268 fig = Figure()
269 fig.canvas = None
269 fig.canvas = None
270 pt.print_figure(fig)
270 pt.print_figure(fig)
271
272
273 @pytest.mark.parametrize(
274 "name, expected_gui, expected_backend",
275 [
276 # name is gui
277 ("gtk3", "gtk3", "gtk3agg"),
278 ("gtk4", "gtk4", "gtk4agg"),
279 ("headless", "headless", "agg"),
280 ("osx", "osx", "macosx"),
281 ("qt", "qt", "qtagg"),
282 ("qt5", "qt5", "qt5agg"),
283 ("qt6", "qt6", "qt6agg"),
284 ("tk", "tk", "tkagg"),
285 ("wx", "wx", "wxagg"),
286 # name is backend
287 ("agg", None, "agg"),
288 ("cairo", None, "cairo"),
289 ("pdf", None, "pdf"),
290 ("ps", None, "ps"),
291 ("svg", None, "svg"),
292 ("template", None, "template"),
293 ("gtk3agg", "gtk3", "gtk3agg"),
294 ("gtk3cairo", "gtk3", "gtk3cairo"),
295 ("gtk4agg", "gtk4", "gtk4agg"),
296 ("gtk4cairo", "gtk4", "gtk4cairo"),
297 ("macosx", "osx", "macosx"),
298 ("nbagg", "nbagg", "nbagg"),
299 ("notebook", "nbagg", "notebook"),
300 ("qtagg", "qt", "qtagg"),
301 ("qtcairo", "qt", "qtcairo"),
302 ("qt5agg", "qt5", "qt5agg"),
303 ("qt5cairo", "qt5", "qt5cairo"),
304 ("qt6agg", "qt", "qt6agg"),
305 ("qt6cairo", "qt", "qt6cairo"),
306 ("tkagg", "tk", "tkagg"),
307 ("tkcairo", "tk", "tkcairo"),
308 ("webagg", "webagg", "webagg"),
309 ("wxagg", "wx", "wxagg"),
310 ("wxcairo", "wx", "wxcairo"),
311 ],
312 )
313 def test_backend_builtin(name, expected_gui, expected_backend):
314 # Test correct identification of Matplotlib built-in backends without importing and using them,
315 # otherwise we would need to ensure all the complex dependencies such as windowing toolkits are
316 # installed.
317
318 mpl_manages_backends = pt._matplotlib_manages_backends()
319 if not mpl_manages_backends:
320 # Backends not supported before _matplotlib_manages_backends or supported
321 # but with different expected_gui or expected_backend.
322 if (
323 name.endswith("agg")
324 or name.endswith("cairo")
325 or name in ("headless", "macosx", "pdf", "ps", "svg", "template")
326 ):
327 pytest.skip()
328 elif name == "qt6":
329 expected_backend = "qtagg"
330 elif name == "notebook":
331 expected_backend, expected_gui = expected_gui, expected_backend
332
333 gui, backend = pt.find_gui_and_backend(name)
334 if not mpl_manages_backends:
335 gui = gui.lower() if gui else None
336 backend = backend.lower() if backend else None
337 assert gui == expected_gui
338 assert backend == expected_backend
339
340
341 def test_backend_entry_point():
342 gui, backend = pt.find_gui_and_backend("inline")
343 assert gui is None
344 expected_backend = (
345 "inline"
346 if pt._matplotlib_manages_backends()
347 else "module://matplotlib_inline.backend_inline"
348 )
349 assert backend == expected_backend
350
351
352 def test_backend_unknown():
353 with pytest.raises(RuntimeError if pt._matplotlib_manages_backends() else KeyError):
354 pt.find_gui_and_backend("name-does-not-exist")
@@ -197,7 +197,7 b' class InteractiveShellEmbed(TerminalInteractiveShell):'
197 dummy=None,
197 dummy=None,
198 stack_depth=1,
198 stack_depth=1,
199 compile_flags=None,
199 compile_flags=None,
200 **kw
200 **kw,
201 ):
201 ):
202 """Activate the interactive interpreter.
202 """Activate the interactive interpreter.
203
203
@@ -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