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 != |
|
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 |
|
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,6 +92,12 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: | |
|
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 | |||
96 | backends_list = list(backends.keys()) |
|
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: |
@@ -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 |
|
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 | |
|
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) | |||
323 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 { |
|
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