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 != |
|
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 |
|
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( |
|
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 |
|
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 | |||
@@ -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 { |
|
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