diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 7a398b25975b..6b6653de83e8 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -266,15 +266,9 @@ def switch_backend(newbackend): rcParamsOrig["backend"] = "agg" return - # Backends are implemented as modules, but "inherit" default method - # implementations from backend_bases._Backend. This is achieved by - # creating a "class" that inherits from backend_bases._Backend and whose - # body is filled with the module's globals. - - backend_name = cbook._backend_module_name(newbackend) - - class backend_mod(matplotlib.backend_bases._Backend): - locals().update(vars(importlib.import_module(backend_name))) + backend_mod = importlib.import_module( + cbook._backend_module_name(newbackend)) + canvas_class = backend_mod.FigureCanvas required_framework = _get_required_interactive_framework(backend_mod) if required_framework is not None: @@ -286,6 +280,36 @@ class backend_mod(matplotlib.backend_bases._Backend): "framework, as {!r} is currently running".format( newbackend, required_framework, current_framework)) + # Load the new_figure_manager(), draw_if_interactive(), and show() + # functions from the backend. + + # Classically, backends can directly export these functions. This should + # keep working for backcompat. + new_figure_manager = getattr(backend_mod, "new_figure_manager", None) + # draw_if_interactive = getattr(backend_mod, "draw_if_interactive", None) + # show = getattr(backend_mod, "show", None) + # In that classical approach, backends are implemented as modules, but + # "inherit" default method implementations from backend_bases._Backend. + # This is achieved by creating a "class" that inherits from + # backend_bases._Backend and whose body is filled with the module globals. + class backend_mod(matplotlib.backend_bases._Backend): + locals().update(vars(backend_mod)) + + # However, the newer approach for defining new_figure_manager (and, in + # the future, draw_if_interactive and show) is to derive them from canvas + # methods. In that case, also update backend_mod accordingly. + if new_figure_manager is None: + def new_figure_manager_given_figure(num, figure): + return canvas_class.new_manager(figure, num) + + def new_figure_manager(num, *args, FigureClass=Figure, **kwargs): + fig = FigureClass(*args, **kwargs) + return new_figure_manager_given_figure(num, fig) + + backend_mod.new_figure_manager_given_figure = \ + new_figure_manager_given_figure + backend_mod.new_figure_manager = new_figure_manager + _log.debug("Loaded backend %s version %s.", newbackend, backend_mod.backend_version) diff --git a/lib/matplotlib/testing/conftest.py b/lib/matplotlib/testing/conftest.py index 01e60fea05e4..d9c4f17e1b27 100644 --- a/lib/matplotlib/testing/conftest.py +++ b/lib/matplotlib/testing/conftest.py @@ -44,13 +44,13 @@ def mpl_test_settings(request): backend = None backend_marker = request.node.get_closest_marker('backend') + prev_backend = matplotlib.get_backend() if backend_marker is not None: assert len(backend_marker.args) == 1, \ "Marker 'backend' must specify 1 backend." backend, = backend_marker.args skip_on_importerror = backend_marker.kwargs.get( 'skip_on_importerror', False) - prev_backend = matplotlib.get_backend() # special case Qt backend importing to avoid conflicts if backend.lower().startswith('qt5'): @@ -87,8 +87,7 @@ def mpl_test_settings(request): try: yield finally: - if backend is not None: - plt.switch_backend(prev_backend) + matplotlib.use(prev_backend) @pytest.fixture diff --git a/lib/matplotlib/tests/test_backend_template.py b/lib/matplotlib/tests/test_backend_template.py new file mode 100644 index 000000000000..31ab644f248f --- /dev/null +++ b/lib/matplotlib/tests/test_backend_template.py @@ -0,0 +1,23 @@ +""" +Backend-loading machinery tests, using variations on the template backend. +""" + +import sys +from types import SimpleNamespace + +import matplotlib as mpl +from matplotlib import pyplot as plt +from matplotlib.backends import backend_template + + +def test_load_template(): + mpl.use("template") + assert type(plt.figure().canvas) == backend_template.FigureCanvasTemplate + + +def test_new_manager(monkeypatch): + mpl_test_backend = SimpleNamespace(**vars(backend_template)) + del mpl_test_backend.new_figure_manager + monkeypatch.setitem(sys.modules, "mpl_test_backend", mpl_test_backend) + mpl.use("module://mpl_test_backend") + assert type(plt.figure().canvas) == backend_template.FigureCanvasTemplate