1212from IPython .core .display import _pngxy
1313from IPython .utils .decorators import flag_calls
1414
15- # If user specifies a GUI, that dictates the backend, otherwise we read the
16- # user's mpl default from the mpl rc structure
17- backends = {
15+
16+ # Matplotlib backend resolution functionality moved from IPython to Matplotlib
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 = {
1821 "tk" : "TkAgg" ,
1922 "gtk" : "GTKAgg" ,
2023 "gtk3" : "GTK3Agg" ,
4144# GUI support to activate based on the desired matplotlib backend. For the
4245# most part it's just a reverse of the above dict, but we also need to add a
4346# 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 ()))
4548# In the reverse mapping, there are a few extra valid matplotlib backends that
4649# map to the same GUI support
47- backend2gui ["GTK" ] = backend2gui ["GTKCairo" ] = "gtk"
48- backend2gui ["GTK3Cairo" ] = "gtk3"
49- backend2gui ["GTK4Cairo" ] = "gtk4"
50- backend2gui ["WX" ] = "wx"
51- backend2gui ["CocoaAgg" ] = "osx"
50+ _deprecated_backend2gui ["GTK" ] = _deprecated_backend2gui ["GTKCairo" ] = "gtk"
51+ _deprecated_backend2gui ["GTK3Cairo" ] = "gtk3"
52+ _deprecated_backend2gui ["GTK4Cairo" ] = "gtk4"
53+ _deprecated_backend2gui ["WX" ] = "wx"
54+ _deprecated_backend2gui ["CocoaAgg" ] = "osx"
5255# There needs to be a hysteresis here as the new QtAgg Matplotlib backend
5356# supports either Qt5 or Qt6 and the IPython qt event loop support Qt4, Qt5,
5457# and Qt6.
55- backend2gui ["QtAgg" ] = "qt"
56- backend2gui ["Qt4Agg" ] = "qt4"
57- backend2gui ["Qt5Agg" ] = "qt5"
58+ _deprecated_backend2gui ["QtAgg" ] = "qt"
59+ _deprecated_backend2gui ["Qt4Agg" ] = "qt4"
60+ _deprecated_backend2gui ["Qt5Agg" ] = "qt5"
5861
5962# And some backends that don't need GUI integration
60- del backend2gui ["nbAgg" ]
61- del backend2gui ["agg" ]
62- del backend2gui ["svg" ]
63- del backend2gui ["pdf" ]
64- del backend2gui ["ps" ]
65- del backend2gui ["module://matplotlib_inline.backend_inline" ]
66- del backend2gui ["module://ipympl.backend_nbagg" ]
63+ del _deprecated_backend2gui ["nbAgg" ]
64+ del _deprecated_backend2gui ["agg" ]
65+ del _deprecated_backend2gui ["svg" ]
66+ del _deprecated_backend2gui ["pdf" ]
67+ del _deprecated_backend2gui ["ps" ]
68+ del _deprecated_backend2gui ["module://matplotlib_inline.backend_inline" ]
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+
6779
6880#-----------------------------------------------------------------------------
6981# Matplotlib utilities
@@ -267,7 +279,7 @@ def select_figure_formats(shell, formats, **kwargs):
267279
268280 [ f .pop (Figure , None ) for f in shell .display_formatter .formatters .values () ]
269281 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' ) :
271283 formatter = shell .display_formatter .ipython_display_formatter
272284 formatter .for_type (Figure , _reshow_nbagg_figure )
273285
@@ -318,9 +330,23 @@ def find_gui_and_backend(gui=None, gui_select=None):
318330 """
319331
320332 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 )
321342
322- has_unified_qt_backend = getattr ( matplotlib , "__version_info__" , ( 0 , 0 )) >= ( 3 , 5 )
343+ return gui , backend
323344
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
324350 backends_ = dict (backends )
325351 if not has_unified_qt_backend :
326352 backends_ ["qt" ] = "qt5agg"
@@ -338,6 +364,7 @@ def find_gui_and_backend(gui=None, gui_select=None):
338364 backend = matplotlib .rcParamsOrig ['backend' ]
339365 # In this case, we need to find what the appropriate gui selection call
340366 # should be for IPython, so we can activate inputhook accordingly
367+ from IPython .core .pylabtools import backend2gui
341368 gui = backend2gui .get (backend , None )
342369
343370 # If we have already had a gui active, we need it and inline are the
@@ -346,6 +373,10 @@ def find_gui_and_backend(gui=None, gui_select=None):
346373 gui = gui_select
347374 backend = backends_ [gui ]
348375
376+ # Since IPython 8.23.0 use None for no gui event loop rather than "inline".
377+ if gui == "inline" :
378+ gui = None
379+
349380 return gui , backend
350381
351382
@@ -431,3 +462,9 @@ def configure_inline_support(shell, backend):
431462 )
432463
433464 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 )
0 commit comments