diff --git a/IPython/core/magics/pylab.py b/IPython/core/magics/pylab.py index 7f7f1d3..265f860 100644 --- a/IPython/core/magics/pylab.py +++ b/IPython/core/magics/pylab.py @@ -93,17 +93,12 @@ class PylabMagics(Magics): """ args = magic_arguments.parse_argstring(self.matplotlib, line) if args.list: - from IPython.core.pylabtools import _matplotlib_manages_backends + from IPython.core.pylabtools import _list_matplotlib_backends_and_gui_loops - if _matplotlib_manages_backends(): - from matplotlib.backends.registry import backend_registry - - backends_list = backend_registry.list_all() - else: - from IPython.core.pylabtools import backends - - backends_list = list(backends.keys()) - print("Available matplotlib backends: %s" % backends_list) + print( + "Available matplotlib backends: %s" + % _list_matplotlib_backends_and_gui_loops() + ) else: gui, backend = self.shell.enable_matplotlib(args.gui.lower() if isinstance(args.gui, str) else args.gui) self._show_matplotlib_backend(args.gui, backend) diff --git a/IPython/core/pylabtools.py b/IPython/core/pylabtools.py index 3135f33..1f5a11f 100644 --- a/IPython/core/pylabtools.py +++ b/IPython/core/pylabtools.py @@ -483,7 +483,8 @@ def _matplotlib_manages_backends() -> bool: If it returns True, the caller can be sure that matplotlib.backends.registry.backend_registry is available along with - member functions resolve_gui_or_backend, resolve_backend and list_all. + member functions resolve_gui_or_backend, resolve_backend, list_all, and + list_gui_frameworks. """ global _matplotlib_manages_backends_value if _matplotlib_manages_backends_value is None: @@ -497,3 +498,21 @@ def _matplotlib_manages_backends() -> bool: _matplotlib_manages_backends_value = False return _matplotlib_manages_backends_value + + +def _list_matplotlib_backends_and_gui_loops() -> list[str]: + """Return list of all Matplotlib backends and GUI event loops. + + This is the list returned by + %matplotlib --list + """ + if _matplotlib_manages_backends(): + from matplotlib.backends.registry import backend_registry + + ret = backend_registry.list_all() + backend_registry.list_gui_frameworks() + else: + from IPython.core import pylabtools + + ret = list(pylabtools.backends.keys()) + + return sorted(["auto"] + ret) diff --git a/IPython/core/shellapp.py b/IPython/core/shellapp.py index 4879e55..99d1d8a 100644 --- a/IPython/core/shellapp.py +++ b/IPython/core/shellapp.py @@ -11,36 +11,45 @@ import glob from itertools import chain import os import sys +import typing as t from traitlets.config.application import boolean_flag from traitlets.config.configurable import Configurable from traitlets.config.loader import Config from IPython.core.application import SYSTEM_CONFIG_DIRS, ENV_CONFIG_DIRS -from IPython.core import pylabtools from IPython.utils.contexts import preserve_keys from IPython.utils.path import filefind from traitlets import ( - Unicode, Instance, List, Bool, CaselessStrEnum, observe, + Unicode, + Instance, + List, + Bool, + CaselessStrEnum, + observe, DottedObjectName, + Undefined, ) from IPython.terminal import pt_inputhooks -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Aliases and Flags -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- gui_keys = tuple(sorted(pt_inputhooks.backends) + sorted(pt_inputhooks.aliases)) -backend_keys: list[str] = [] - shell_flags = {} addflag = lambda *args: shell_flags.update(boolean_flag(*args)) -addflag('autoindent', 'InteractiveShell.autoindent', - 'Turn on autoindenting.', 'Turn off autoindenting.' +addflag( + "autoindent", + "InteractiveShell.autoindent", + "Turn on autoindenting.", + "Turn off autoindenting.", ) -addflag('automagic', 'InteractiveShell.automagic', - """Turn on the auto calling of magic commands. Type %%magic at the +addflag( + "automagic", + "InteractiveShell.automagic", + """Turn on the auto calling of magic commands. Type %%magic at the IPython prompt for more information.""", 'Turn off the auto calling of magic commands.' ) @@ -96,6 +105,37 @@ shell_aliases = dict( ) shell_aliases['cache-size'] = 'InteractiveShell.cache_size' + +# ----------------------------------------------------------------------------- +# Traitlets +# ----------------------------------------------------------------------------- + + +class MatplotlibBackendCaselessStrEnum(CaselessStrEnum): + """An enum of Matplotlib backend strings where the case should be ignored. + + Prior to Matplotlib 3.9.1 the list of valid backends is hardcoded in + pylabtools.backends. After that, Matplotlib manages backends. + + The list of valid backends is determined when it is first needed to avoid + wasting unnecessary initialisation time. + """ + + def __init__( + self: CaselessStrEnum[t.Any], + default_value: t.Any = Undefined, + **kwargs: t.Any, + ) -> None: + super().__init__(None, default_value=default_value, **kwargs) + + def __getattribute__(self, name): + if name == "values" and object.__getattribute__(self, name) is None: + from IPython.core.pylabtools import _list_matplotlib_backends_and_gui_loops + + self.values = _list_matplotlib_backends_and_gui_loops() + return object.__getattribute__(self, name) + + #----------------------------------------------------------------------------- # Main classes and functions #----------------------------------------------------------------------------- @@ -155,30 +195,31 @@ class InteractiveShellApp(Configurable): exec_lines = List(Unicode(), help="""lines of code to run at IPython startup.""" ).tag(config=True) - code_to_run = Unicode('', - help="Execute the given command string." - ).tag(config=True) - module_to_run = Unicode('', - help="Run the module as a script." + code_to_run = Unicode("", help="Execute the given command string.").tag(config=True) + module_to_run = Unicode("", help="Run the module as a script.").tag(config=True) + gui = CaselessStrEnum( + gui_keys, + allow_none=True, + help="Enable GUI event loop integration with any of {0}.".format(gui_keys), ).tag(config=True) - gui = CaselessStrEnum(gui_keys, allow_none=True, - help="Enable GUI event loop integration with any of {0}.".format(gui_keys) - ).tag(config=True) - matplotlib = CaselessStrEnum(backend_keys, allow_none=True, + matplotlib = MatplotlibBackendCaselessStrEnum( + allow_none=True, help="""Configure matplotlib for interactive use with - the default matplotlib backend.""" + the default matplotlib backend.""", ).tag(config=True) - pylab = CaselessStrEnum(backend_keys, allow_none=True, + pylab = MatplotlibBackendCaselessStrEnum( + allow_none=True, help="""Pre-load matplotlib and numpy for interactive use, selecting a particular matplotlib backend and loop integration. - """ + """, ).tag(config=True) - pylab_import_all = Bool(True, + pylab_import_all = Bool( + True, help="""If true, IPython will populate the user namespace with numpy, pylab, etc. and an ``import *`` is done from numpy and pylab, when using pylab mode. When False, pylab mode should not import any names into the user namespace. - """ + """, ).tag(config=True) ignore_cwd = Bool( False,