From 1254075c5b8d80cc020b2156ec24244d797f398f Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 26 Oct 2018 11:22:20 +0200 Subject: [PATCH 1/2] Tell IPython the correct GUI event loop to use for all backends. IPython currently uses a hard-coded table (IPython.core.pylabtools.backend2gui) to know which event loop to use for which backend. This approach fails for both the new builtin cairo-based backends (which do not appear in the table), and for third-party backends (e.g. mplcairo). mplcairo has used some custom code to patch that table for a while; reuse it to more generally use the new "required_interactive_framework" attribute. Note that this PR suggests that there should be a better way to go back from the canvas class to the backend module (rather than looking for `sys.modules[cls.__module__].required_interactive_framework`, which is a bit hacky and could in theory fail if the canvas class is actually defined in another submodule). --- lib/matplotlib/backend_bases.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 89587c7a483d..2d44f0cd511b 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -34,6 +34,7 @@ from contextlib import contextmanager from enum import IntEnum +import functools import importlib import io import logging @@ -44,6 +45,7 @@ import numpy as np +import matplotlib as mpl from matplotlib import ( backend_tools as tools, cbook, colors, textpath, tight_bbox, transforms, widgets, get_backend, is_interactive, rcParams) @@ -1564,6 +1566,7 @@ class FigureCanvasBase(object): 'Tagged Image File Format') def __init__(self, figure): + self._fix_ipython_backend2gui() self._is_idle_drawing = True self._is_saving = False figure.set_canvas(self) @@ -1580,6 +1583,35 @@ def __init__(self, figure): self.toolbar = None # NavigationToolbar2 will set me self._is_idle_drawing = False + @classmethod + @functools.lru_cache() + def _fix_ipython_backend2gui(cls): + # Fix hard-coded module -> toolkit mapping in IPython (used for + # `ipython --auto`). This cannot be done at import time due to + # ordering issues, so we do it when creating a canvas, and should only + # be done once per class (hence the `lru_cache(1)`). + if "IPython" not in sys.modules: + return + import IPython + ip = IPython.get_ipython() + if not ip: + return + from IPython.core import pylabtools as pt + backend_mod = sys.modules[cls.__module__] + rif = getattr(backend_mod, "required_interactive_framework", None) + backend2gui_rif = {"qt5": "qt", "qt4": "qt", "gtk3": "gtk3", + "wx": "wx", "macosx": "osx"}.get(rif) + if backend2gui_rif: + pt.backend2gui[get_backend()] = backend2gui_rif + # Work around pylabtools.find_gui_and_backend always reading from + # rcParamsOrig. + orig_origbackend = mpl.rcParamsOrig["backend"] + try: + mpl.rcParamsOrig["backend"] = mpl.rcParams["backend"] + ip.enable_matplotlib() + finally: + mpl.rcParamsOrig["backend"] = orig_origbackend + @contextmanager def _idle_draw_cntx(self): self._is_idle_drawing = True From b614c858dbd31b5ef82511e4a5b5bfea75925895 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 4 Mar 2019 22:08:09 +0100 Subject: [PATCH 2/2] Be more robust against possible future IPython API changes. --- lib/matplotlib/backend_bases.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 2d44f0cd511b..dcb3637498e6 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1597,6 +1597,11 @@ def _fix_ipython_backend2gui(cls): if not ip: return from IPython.core import pylabtools as pt + if (not hasattr(pt, "backend2gui") + or not hasattr(ip, "enable_matplotlib")): + # In case we ever move the patch to IPython and remove these APIs, + # don't break on our side. + return backend_mod = sys.modules[cls.__module__] rif = getattr(backend_mod, "required_interactive_framework", None) backend2gui_rif = {"qt5": "qt", "qt4": "qt", "gtk3": "gtk3",