Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 2ba8545

Browse filesBrowse files
authored
Merge pull request #27948 from ianthomas23/entry_points
Move IPython backend mapping to Matplotlib and support entry points
2 parents 378a77a + e458aa6 commit 2ba8545
Copy full SHA for 2ba8545

24 files changed

+803
-98
lines changed

‎.github/workflows/tests.yml

Copy file name to clipboardExpand all lines: .github/workflows/tests.yml
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ jobs:
5959
delete-font-cache: true
6060
- os: ubuntu-20.04
6161
python-version: 3.9
62-
extra-requirements: '-r requirements/testing/extra.txt'
62+
# One CI run tests ipython/matplotlib-inline before backend mapping moved to mpl
63+
extra-requirements: '-r requirements/testing/extra.txt "ipython<8.24" "matplotlib-inline<0.1.7"'
6364
CFLAGS: "-fno-lto" # Ensure that disabling LTO works.
6465
# https://github.com/matplotlib/matplotlib/pull/26052#issuecomment-1574595954
6566
# https://www.riverbankcomputing.com/pipermail/pyqt/2023-November/045606.html

‎doc/users/next_whats_new/backend_registry.rst

Copy file name to clipboardExpand all lines: doc/users/next_whats_new/backend_registry.rst
+12-1Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,15 @@ BackendRegistry
33

44
New :class:`~matplotlib.backends.registry.BackendRegistry` class is the single
55
source of truth for available backends. The singleton instance is
6-
``matplotlib.backends.backend_registry``.
6+
``matplotlib.backends.backend_registry``. It is used internally by Matplotlib,
7+
and also IPython (and therefore Jupyter) starting with IPython 8.24.0.
8+
9+
There are three sources of backends: built-in (source code is within the
10+
Matplotlib repository), explicit ``module://some.backend`` syntax (backend is
11+
obtained by loading the module), or via an entry point (self-registering
12+
backend in an external package).
13+
14+
To obtain a list of all registered backends use:
15+
16+
>>> from matplotlib.backends import backend_registry
17+
>>> backend_registry.list_all()

‎galleries/users_explain/figure/backends.rst

Copy file name to clipboardExpand all lines: galleries/users_explain/figure/backends.rst
+9-5Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,8 @@ QtAgg Agg rendering in a Qt_ canvas (requires PyQt_ or `Qt for Python`_,
175175
more details.
176176
ipympl Agg rendering embedded in a Jupyter widget (requires ipympl_).
177177
This backend can be enabled in a Jupyter notebook with
178-
``%matplotlib ipympl``.
178+
``%matplotlib ipympl`` or ``%matplotlib widget``. Works with
179+
Jupyter ``lab`` and ``notebook>=7``.
179180
GTK3Agg Agg rendering to a GTK_ 3.x canvas (requires PyGObject_ and
180181
pycairo_). This backend can be activated in IPython with
181182
``%matplotlib gtk3``.
@@ -188,7 +189,8 @@ TkAgg Agg rendering to a Tk_ canvas (requires TkInter_). This
188189
backend can be activated in IPython with ``%matplotlib tk``.
189190
nbAgg Embed an interactive figure in a Jupyter classic notebook. This
190191
backend can be enabled in Jupyter notebooks via
191-
``%matplotlib notebook``.
192+
``%matplotlib notebook`` or ``%matplotlib nbagg``. Works with
193+
Jupyter ``notebook<7`` and ``nbclassic``.
192194
WebAgg On ``show()`` will start a tornado server with an interactive
193195
figure.
194196
GTK3Cairo Cairo rendering to a GTK_ 3.x canvas (requires PyGObject_ and
@@ -200,7 +202,7 @@ wxAgg Agg rendering to a wxWidgets_ canvas (requires wxPython_ 4).
200202
========= ================================================================
201203

202204
.. note::
203-
The names of builtin backends case-insensitive; e.g., 'QtAgg' and
205+
The names of builtin backends are case-insensitive; e.g., 'QtAgg' and
204206
'qtagg' are equivalent.
205207

206208
.. _`Anti-Grain Geometry`: http://agg.sourceforge.net/antigrain.com/
@@ -222,11 +224,13 @@ wxAgg Agg rendering to a wxWidgets_ canvas (requires wxPython_ 4).
222224
.. _wxWidgets: https://www.wxwidgets.org/
223225
.. _ipympl: https://www.matplotlib.org/ipympl
224226

227+
.. _ipympl_install:
228+
225229
ipympl
226230
^^^^^^
227231

228-
The Jupyter widget ecosystem is moving too fast to support directly in
229-
Matplotlib. To install ipympl:
232+
The ipympl backend is in a separate package that must be explicitly installed
233+
if you wish to use it, for example:
230234

231235
.. code-block:: bash
232236

‎galleries/users_explain/figure/figure_intro.rst

Copy file name to clipboardExpand all lines: galleries/users_explain/figure/figure_intro.rst
+14-17Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,20 @@ Notebooks and IDEs
5252

5353
If you are using a Notebook (e.g. `Jupyter <https://jupyter.org>`_) or an IDE
5454
that renders Notebooks (PyCharm, VSCode, etc), then they have a backend that
55-
will render the Matplotlib Figure when a code cell is executed. One thing to
56-
be aware of is that the default Jupyter backend (``%matplotlib inline``) will
55+
will render the Matplotlib Figure when a code cell is executed. The default
56+
Jupyter backend (``%matplotlib inline``) creates static plots that
5757
by default trim or expand the figure size to have a tight box around Artists
58-
added to the Figure (see :ref:`saving_figures`, below). If you use a backend
59-
other than the default "inline" backend, you will likely need to use an ipython
60-
"magic" like ``%matplotlib notebook`` for the Matplotlib :ref:`notebook
61-
<jupyter_notebooks_jupyterlab>` or ``%matplotlib widget`` for the `ipympl
62-
<https://matplotlib.org/ipympl/>`_ backend.
58+
added to the Figure (see :ref:`saving_figures`, below). For interactive plots
59+
in Jupyter you will need to use an ipython "magic" like ``%matplotlib widget``
60+
for the `ipympl <https://matplotlib.org/ipympl/>`_ backend in ``jupyter lab``
61+
or ``notebook>=7``, or ``%matplotlib notebook`` for the Matplotlib
62+
:ref:`notebook <jupyter_notebooks_jupyterlab>` in ``notebook<7`` or
63+
``nbclassic``.
64+
65+
.. note::
66+
67+
The `ipympl <https://matplotlib.org/ipympl/>`_ backend is in a separate
68+
package, see :ref:`Installing ipympl <ipympl_install>`.
6369

6470
.. figure:: /_static/FigureNotebook.png
6571
:alt: Image of figure generated in Jupyter Notebook with notebook
@@ -75,15 +81,6 @@ other than the default "inline" backend, you will likely need to use an ipython
7581
.. seealso::
7682
:ref:`interactive_figures`.
7783

78-
.. note::
79-
80-
If you only need to use the classic notebook (i.e. ``notebook<7``),
81-
you can use:
82-
83-
.. sourcecode:: ipython
84-
85-
%matplotlib notebook
86-
8784
.. _standalone-scripts-and-interactive-use:
8885

8986
Standalone scripts and interactive use
@@ -104,7 +101,7 @@ backend. These are typically chosen either in the user's :ref:`matplotlibrc
104101
QtAgg backend.
105102

106103
When run from a script, or interactively (e.g. from an
107-
`iPython shell <https://ipython.readthedocs.io/en/stable/>`_) the Figure
104+
`IPython shell <https://ipython.readthedocs.io/en/stable/>`_) the Figure
108105
will not be shown until we call ``plt.show()``. The Figure will appear in
109106
a new GUI window, and usually will have a toolbar with Zoom, Pan, and other tools
110107
for interacting with the Figure. By default, ``plt.show()`` blocks

‎galleries/users_explain/figure/writing_a_backend_pyplot_interface.rst

Copy file name to clipboardExpand all lines: galleries/users_explain/figure/writing_a_backend_pyplot_interface.rst
+44Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,47 @@ Function-based API
8484
2. **Showing figures**: `.pyplot.show()` calls a module-level ``show()``
8585
function, which is typically generated via the ``ShowBase`` class and its
8686
``mainloop`` method.
87+
88+
Registering a backend
89+
---------------------
90+
91+
For a new backend to be usable via ``matplotlib.use()`` or IPython
92+
``%matplotlib`` magic command, it must be compatible with one of the three ways
93+
supported by the :class:`~matplotlib.backends.registry.BackendRegistry`:
94+
95+
Built-in
96+
^^^^^^^^
97+
98+
A backend built into Matplotlib must have its name and
99+
``FigureCanvas.required_interactive_framework`` hard-coded in the
100+
:class:`~matplotlib.backends.registry.BackendRegistry`. If the backend module
101+
is not ``f"matplotlib.backends.backend_{backend_name.lower()}"`` then there
102+
must also be an entry in the ``BackendRegistry._name_to_module``.
103+
104+
module:// syntax
105+
^^^^^^^^^^^^^^^^
106+
107+
Any backend in a separate module (not built into Matplotlib) can be used by
108+
specifying the path to the module in the form ``module://some.backend.module``.
109+
An example is ``module://mplcairo.qt`` for
110+
`mplcairo <https:////github.com/matplotlib/mplcairo>`_. The backend's
111+
interactive framework will be taken from its
112+
``FigureCanvas.required_interactive_framework``.
113+
114+
Entry point
115+
^^^^^^^^^^^
116+
117+
An external backend module can self-register as a backend using an
118+
``entry point`` in its ``pyproject.toml`` such as the one used by
119+
``matplotlib-inline``:
120+
121+
.. code-block:: toml
122+
123+
[project.entry-points."matplotlib.backend"]
124+
inline = "matplotlib_inline.backend_inline"
125+
126+
The backend's interactive framework will be taken from its
127+
``FigureCanvas.required_interactive_framework``. All entry points are loaded
128+
together but only when first needed, such as when a backend name is not
129+
recognised as a built-in backend, or when
130+
:meth:`~matplotlib.backends.registry.BackendRegistry.list_all` is first called.

‎lib/matplotlib/__init__.py

Copy file name to clipboardExpand all lines: lib/matplotlib/__init__.py
+3-1Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1208,14 +1208,16 @@ def use(backend, *, force=True):
12081208
backend names, which are case-insensitive:
12091209
12101210
- interactive backends:
1211-
GTK3Agg, GTK3Cairo, GTK4Agg, GTK4Cairo, MacOSX, nbAgg, QtAgg,
1211+
GTK3Agg, GTK3Cairo, GTK4Agg, GTK4Cairo, MacOSX, nbAgg, notebook, QtAgg,
12121212
QtCairo, TkAgg, TkCairo, WebAgg, WX, WXAgg, WXCairo, Qt5Agg, Qt5Cairo
12131213
12141214
- non-interactive backends:
12151215
agg, cairo, pdf, pgf, ps, svg, template
12161216
12171217
or a string of the form: ``module://my.module.name``.
12181218
1219+
notebook is a synonym for nbAgg.
1220+
12191221
Switching to an interactive backend is not possible if an unrelated
12201222
event loop has already been started (e.g., switching to GTK3Agg if a
12211223
TkAgg window has already been opened). Switching to a non-interactive

‎lib/matplotlib/backend_bases.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backend_bases.py
+11-4Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1766,8 +1766,16 @@ def _fix_ipython_backend2gui(cls):
17661766
# `ipython --auto`). This cannot be done at import time due to
17671767
# ordering issues, so we do it when creating a canvas, and should only
17681768
# be done once per class (hence the `cache`).
1769-
if sys.modules.get("IPython") is None:
1769+
1770+
# This function will not be needed when Python 3.12, the latest version
1771+
# supported by IPython < 8.24, reaches end-of-life in late 2028.
1772+
# At that time this function can be made a no-op and deprecated.
1773+
mod_ipython = sys.modules.get("IPython")
1774+
if mod_ipython is None or mod_ipython.version_info[:2] >= (8, 24):
1775+
# Use of backend2gui is not needed for IPython >= 8.24 as the
1776+
# functionality has been moved to Matplotlib.
17701777
return
1778+
17711779
import IPython
17721780
ip = IPython.get_ipython()
17731781
if not ip:
@@ -2030,9 +2038,8 @@ def _switch_canvas_and_return_print_method(self, fmt, backend=None):
20302038
canvas = None
20312039
if backend is not None:
20322040
# Return a specific canvas class, if requested.
2033-
canvas_class = (
2034-
importlib.import_module(cbook._backend_module_name(backend))
2035-
.FigureCanvas)
2041+
from .backends.registry import backend_registry
2042+
canvas_class = backend_registry.load_backend_module(backend).FigureCanvas
20362043
if not hasattr(canvas_class, f"print_{fmt}"):
20372044
raise ValueError(
20382045
f"The {backend!r} backend does not support {fmt} output")

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.