Skip to content

[Bug]: VSCode matplotlib interactive mode cannot import Qt bindings #25673

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
kwsp opened this issue Apr 13, 2023 · 22 comments · Fixed by #25772
Closed

[Bug]: VSCode matplotlib interactive mode cannot import Qt bindings #25673

kwsp opened this issue Apr 13, 2023 · 22 comments · Fixed by #25772

Comments

@kwsp
Copy link
Contributor

kwsp commented Apr 13, 2023

Bug summary

Running in VS Code interactive mode, %matplotlib qt throws an exception ImportError: Failed to import any of the following Qt binding modules: PyQt6, PySide6, PyQt5, PySide2. However, PySide6 is installed.

Error trace
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
[c:\Users\nhl08\code\oct-invivo-analysis\flatten.py](file:///C:/Users/nhl08/code/oct-invivo-analysis/flatten.py) in line 12
      [13](file:///c%3A/Users/nhl08/code/oct-invivo-analysis/flatten.py?line=12) import matplotlib.pyplot as plt
     [15](file:///c%3A/Users/nhl08/code/oct-invivo-analysis/flatten.py?line=14) from oct_utils import imshow, imshow2
---> [17](file:///c%3A/Users/nhl08/code/oct-invivo-analysis/flatten.py?line=16) get_ipython().run_line_magic('matplotlib', 'qt')

File [c:\Users\nhl08\miniconda3\envs\sim\lib\site-packages\IPython\core\interactiveshell.py:2414](file:///C:/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/interactiveshell.py:2414), in InteractiveShell.run_line_magic(self, magic_name, line, _stack_depth)
   [2412](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/interactiveshell.py?line=2411)     kwargs['local_ns'] = self.get_local_scope(stack_depth)
   [2413](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/interactiveshell.py?line=2412) with self.builtin_trap:
-> [2414](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/interactiveshell.py?line=2413)     result = fn(*args, **kwargs)
   [2416](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/interactiveshell.py?line=2415) # The code below prevents the output from being displayed
   [2417](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/interactiveshell.py?line=2416) # when using magics with decodator @output_can_be_silenced
   [2418](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/interactiveshell.py?line=2417) # when the last Python token in the expression is a ';'.
   [2419](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/interactiveshell.py?line=2418) if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):

File [c:\Users\nhl08\miniconda3\envs\sim\lib\site-packages\IPython\core\magics\pylab.py:99](file:///C:/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/magics/pylab.py:99), in PylabMagics.matplotlib(self, line)
     [97](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/magics/pylab.py?line=96)     print("Available matplotlib backends: %s" % backends_list)
     [98](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/magics/pylab.py?line=97) else:
---> [99](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/magics/pylab.py?line=98)     gui, backend = self.shell.enable_matplotlib(args.gui.lower() if isinstance(args.gui, str) else args.gui)
    [100](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/magics/pylab.py?line=99)     self._show_matplotlib_backend(args.gui, backend)

File [c:\Users\nhl08\miniconda3\envs\sim\lib\site-packages\IPython\core\interactiveshell.py:3600](file:///C:/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/interactiveshell.py:3600), in InteractiveShell.enable_matplotlib(self, gui)
   [3596](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/interactiveshell.py?line=3595)         print('Warning: Cannot change to a different GUI toolkit: %s.'
   [3597](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/interactiveshell.py?line=3596)                 ' Using %s instead.' % (gui, self.pylab_gui_select))
   [3598](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/interactiveshell.py?line=3597)         gui, backend = pt.find_gui_and_backend(self.pylab_gui_select)
-> [3600](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/interactiveshell.py?line=3599) pt.activate_matplotlib(backend)
   [3601](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/interactiveshell.py?line=3600) configure_inline_support(self, backend)
   [3603](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/interactiveshell.py?line=3602) # Now we must activate the gui pylab wants to use, and fix %run to take
   [3604](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/interactiveshell.py?line=3603) # plot updates into account

File [c:\Users\nhl08\miniconda3\envs\sim\lib\site-packages\IPython\core\pylabtools.py:360](file:///C:/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/pylabtools.py:360), in activate_matplotlib(backend)
    [355](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/pylabtools.py?line=354) # Due to circular imports, pyplot may be only partially initialised
    [356](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/pylabtools.py?line=355) # when this function runs.
    [357](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/pylabtools.py?line=356) # So avoid needing matplotlib attribute-lookup to access pyplot.
    [358](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/pylabtools.py?line=357) from matplotlib import pyplot as plt
--> [360](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/pylabtools.py?line=359) plt.switch_backend(backend)
    [362](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/pylabtools.py?line=361) plt.show._needmain = False
    [363](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/pylabtools.py?line=362) # We need to detect at runtime whether show() is called by the user.
    [364](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/IPython/core/pylabtools.py?line=363) # For this, we wrap it into a decorator which adds a 'called' flag.

File [c:\Users\nhl08\miniconda3\envs\sim\lib\site-packages\matplotlib\pyplot.py:271](file:///C:/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/pyplot.py:271), in switch_backend(newbackend)
    [268](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/pyplot.py?line=267) # have to escape the switch on access logic
    [269](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/pyplot.py?line=268) old_backend = dict.__getitem__(rcParams, 'backend')
--> [271](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/pyplot.py?line=270) backend_mod = importlib.import_module(
    [272](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/pyplot.py?line=271)     cbook._backend_module_name(newbackend))
    [274](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/pyplot.py?line=273) required_framework = _get_required_interactive_framework(backend_mod)
    [275](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/pyplot.py?line=274) if required_framework is not None:

File [c:\Users\nhl08\miniconda3\envs\sim\lib\importlib\__init__.py:126](file:///C:/Users/nhl08/miniconda3/envs/sim/lib/importlib/__init__.py:126), in import_module(name, package)
    [124](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/importlib/__init__.py?line=123)             break
    [125](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/importlib/__init__.py?line=124)         level += 1
--> [126](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/importlib/__init__.py?line=125) return _bootstrap._gcd_import(name[level:], package, level)

File :1050, in _gcd_import(name, package, level)

File :1027, in _find_and_load(name, import_)

File :1006, in _find_and_load_unlocked(name, import_)

File :688, in _load_unlocked(spec)

File :883, in exec_module(self, module)

File :241, in _call_with_frames_removed(f, *args, **kwds)

File [c:\Users\nhl08\miniconda3\envs\sim\lib\site-packages\matplotlib\backends\backend_qt5agg.py:7](file:///C:/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/backend_qt5agg.py:7)
      [4](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/backend_qt5agg.py?line=3) from .. import backends
      [6](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/backend_qt5agg.py?line=5) backends._QT_FORCE_QT5_BINDING = True
----> [7](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/backend_qt5agg.py?line=6) from .backend_qtagg import (    # noqa: F401, E402 # pylint: disable=W0611
      [8](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/backend_qt5agg.py?line=7)     _BackendQTAgg, FigureCanvasQTAgg, FigureManagerQT, NavigationToolbar2QT,
      [9](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/backend_qt5agg.py?line=8)     FigureCanvasAgg, FigureCanvasQT)
     [12](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/backend_qt5agg.py?line=11) @_BackendQTAgg.export
     [13](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/backend_qt5agg.py?line=12) class _BackendQT5Agg(_BackendQTAgg):
     [14](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/backend_qt5agg.py?line=13)     pass

File [c:\Users\nhl08\miniconda3\envs\sim\lib\site-packages\matplotlib\backends\backend_qtagg.py:9](file:///C:/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/backend_qtagg.py:9)
      [5](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/backend_qtagg.py?line=4) import ctypes
      [7](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/backend_qtagg.py?line=6) from matplotlib.transforms import Bbox
----> [9](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/backend_qtagg.py?line=8) from .qt_compat import QT_API, _enum
     [10](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/backend_qtagg.py?line=9) from .backend_agg import FigureCanvasAgg
     [11](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/backend_qtagg.py?line=10) from .backend_qt import QtCore, QtGui, _BackendQT, FigureCanvasQT

File [c:\Users\nhl08\miniconda3\envs\sim\lib\site-packages\matplotlib\backends\qt_compat.py:135](file:///C:/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/qt_compat.py:135)
    [133](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/qt_compat.py?line=132)         break
    [134](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/qt_compat.py?line=133)     else:
--> [135](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/qt_compat.py?line=134)         raise ImportError(
    [136](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/qt_compat.py?line=135)             "Failed to import any of the following Qt binding modules: {}"
    [137](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/qt_compat.py?line=136)             .format(", ".join(_ETS.values())))
    [138](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/qt_compat.py?line=137) else:  # We should not get there.
    [139](file:///c%3A/Users/nhl08/miniconda3/envs/sim/lib/site-packages/matplotlib/backends/qt_compat.py?line=138)     raise AssertionError(f"Unexpected QT_API: {QT_API}")

ImportError: Failed to import any of the following Qt binding modules: PyQt6, PySide6, PyQt5, PySide2

Code for reproduction

Run in a VS Code interactive cell

# %%
import matplotlib.pyplot as plt

%matplotlib qt

Additional information

ipykernel: 6.22.0
ipython: 8.12.0
ipywidgets: 8.0.6

Operating system

Windows 10 and Windows 11 (2 separate computers)

Matplotlib Version

3.7.1

Matplotlib Backend

Qt5Agg

Python version

3.10.10

Jupyter version

No response

Installation

pip

@tacaswell
Copy link
Member

Are you sure that pyside6 is installed in the environment that is being used for the terminal.

@kwsp
Copy link
Contributor Author

kwsp commented Apr 13, 2023

Yes. In the same interactive shell, I can run the following cell

# %%
import PySide6
print(PySide6.__version__)  # Prints 6.5.0

@kwsp
Copy link
Contributor Author

kwsp commented Apr 13, 2023

When I start python from the command line and try to plot things, interactive plots work. It's only in VS Code interactive mode, when I try to enable interactive plots with %matplotlib qt does this happen.

@tacaswell
Copy link
Member

If you import pyside6 before you do %matplotlib qt does it work?

@kwsp
Copy link
Contributor Author

kwsp commented Apr 13, 2023

It does not unfortunately. Just tested in a fresh shell:

# %%
import PySide6
import matplotlib.pyplot as plt

%matplotlib qt
ImportError: Failed to import any of the following Qt binding modules: PyQt6, PySide6, PyQt5, PySide2

I also want to add that this has been happening to me for many months, on two separate machines (Windows 10 and Windows 11).

@tacaswell
Copy link
Member

Can you try the code in

elif QT_API == QT_API_PYSIDE6:
from PySide6 import QtCore, QtGui, QtWidgets, __version__
import shiboken6
def _isdeleted(obj): return not shiboken6.isValid(obj)
if parse_version(__version__) >= parse_version('6.4'):
_to_int = operator.attrgetter('value')
else:
_to_int = int
to verify that works as expected?

does %gui qt work?

Does

import PySide6
import matplotlib.pyplot as plt
plt.ion()
fig, ax = plt.subplots()

work?

What version of pyside6?

In the shells where it works can you check

fig = plt.gcf() # or get a Figure object however you want
print(type(fig.canvas).mro()

to make sure it really is using pyside6 in those cases.

Sorry for asking many questions, I do not have a window system set up to reproduce this.

@kwsp
Copy link
Contributor Author

kwsp commented Apr 13, 2023

Can you try the code in

elif QT_API == QT_API_PYSIDE6:
from PySide6 import QtCore, QtGui, QtWidgets, __version__
import shiboken6
def _isdeleted(obj): return not shiboken6.isValid(obj)
if parse_version(__version__) >= parse_version('6.4'):
_to_int = operator.attrgetter('value')
else:
_to_int = int

to verify that works as expected?

This works if I run directly in the shell.

does %gui qt work?

This line runs without exception, but matplotlib would still use the Agg backend.

Does

import PySide6
import matplotlib.pyplot as plt
plt.ion()
fig, ax = plt.subplots()

work?

This executes without exception, but the result plot is still inline.

What version of pyside6?

6.5.0

In the shells where it works can you check

fig = plt.gcf() # or get a Figure object however you want
print(type(fig.canvas).mro()

to make sure it really is using pyside6 in those cases.

It's not using the PySide6 backend: [<class 'matplotlib.backends.backend_agg.FigureCanvasAgg'>, <class 'matplotlib.backend_bases.FigureCanvasBase'>, <class 'object'>]

Sorry for asking many questions, I do not have a window system set up to reproduce this.

No I understand. Thanks for your help. I'm an absolute noob with Windows but I need to develop native Windows apps for work :(

@tacaswell
Copy link
Member

Can you get a Pyside6 "hello world" app to work in the vscode terminal?

Another thing I just noticed is that there is "conda" in your paths, but you said you installed via pip.

Try making a fresh environment and installing everything from conda. Mixing conda and wheels can go bad in odd ways (see https://pypackaging-native.github.io).

Can you try older versions of pyside?

If you use

plt.switch('qtagg')

early does that make any difference?

@kwsp
Copy link
Contributor Author

kwsp commented Apr 24, 2023

plt.switch_backend('qtagg') gave the same error: ImportError: Failed to import any of the following Qt binding modules: PyQt6, PySide6, PyQt5, PySide2

In the same VS Code interactive shell, I can run the following hello world program with PySide6 and a Qt window pops up.

from PySide6 import QtWidgets

app = QtWidgets.QApplication()
win = QtWidgets.QWidget()
win.show()
app.exec()

This actually isn't specific to Windows and the above results are from an Ubuntu 22.04 machine.

@kwsp
Copy link
Contributor Author

kwsp commented Apr 24, 2023

The reason I'm using conda + pip:

  1. Conda is great at managing virtual environments. pip doesn't do that.
  2. My packages are all declared with a pyproject.toml file. I can then easily install my packages with pip install . and have pip manage the dependencies for me. I have yet to find a painless way to manage my own packages with conda.

That said, in this case, I'm exclusively using pip to manage packages in this environment and only using conda as the python version/virtual environment manager, which I heard is innocuous.

Re: mixing conda env and pip - I just took a look, even matplotlib (when using conda virtual environments) needs to use pip install -e . to install matplotlib...: https://matplotlib.org/devdocs/devel/development_setup.html#create-a-dedicated-environment

@tacaswell
Copy link
Member

I can not reproduce this (I got the interactive window by right click -> "run in interactive window" on an empty file and selected the system Python (which on my system has enough of the stack installed) for the Python that vscode is using).

Looking at the code in qt_compat.py can you sort out exactly what is failing when we try each of the bindings?

@kwsp
Copy link
Contributor Author

kwsp commented Apr 25, 2023

I looked into qt_compat.py, and when I execute

import matplotlib.backends.qt_compat
matplotlib.backends.qt_compat._setup_pyqt5plus()

I get the same exception.

However, I just found a fix! Since I know I'm using PySide6, I can run this line

from PySide6 import QtCore, QtGui, QtWidgets, __version__
no problem, since PySide6 has always been installed. Once I do that, the above imports of qt_compat._setup_pyqt5plus() works and %matplotlib qt works.

from PySide6 import QtCore, QtGui, QtWidgets, __version__
import shiboken6

@kwsp
Copy link
Contributor Author

kwsp commented Apr 25, 2023

Ok I stepped through qt_compat.py with a debugger and found the issue.

When I run %matplotlib qt, mpl loads qt_compat.py which in turn executes the file.

if sys.modules.get("PyQt6.QtCore"):
QT_API = QT_API_PYQT6
elif sys.modules.get("PySide6.QtCore"):
QT_API = QT_API_PYSIDE6
elif sys.modules.get("PyQt5.QtCore"):
QT_API = QT_API_PYQT5
elif sys.modules.get("PySide2.QtCore"):
QT_API = QT_API_PYSIDE2

After this, QT_API remains undefined because no Qt bindings have been imported yet. So, we fall through to this case:

# Otherwise, check the QT_API environment variable (from Enthought). This can
# only override the binding, not the backend (in other words, we check that the
# requested backend actually matches). Use _get_backend_or_none to avoid
# triggering backend resolution (which can result in a partially but
# incompletely imported backend_qt5).
elif (mpl.rcParams._get_backend_or_none() or "").lower().startswith("qt5"):
if QT_API_ENV in ["pyqt5", "pyside2"]:
QT_API = _ETS[QT_API_ENV]
else:
_QT_FORCE_QT5_BINDING = True # noqa
QT_API = None
# A non-Qt backend was selected but we still got there (possible, e.g., when
# fully manually embedding Matplotlib in a Qt app without using pyplot).

For my environment, mpl.rcParams._get_backend_or_none() returns Qt5Agg, even though I have no Qt5 bindings installed. Still, we run into this case

_QT_FORCE_QT5_BINDING = True # noqa

So now, QT_API = None and _QT_FORCE_QT5_BINDING = True, and we fall though to this case while _setup_qt5plus() is never executed.

if _QT_FORCE_QT5_BINDING:

Obviously, we now get to this line since no Qt5 bindings are installed.

My work around of importing PySide6.QtCore manually above basically negates this issue because once Qt6 bindings are in sys.modules, qt_compat.py would set QT_API = QT_API_PYSIDE6.

@kwsp
Copy link
Contributor Author

kwsp commented Apr 25, 2023

On a first pass look, it seems for me this is caused by

_QT_FORCE_QT5_BINDING = True # noqa

which if I check git blame, you committed 2bc0c1c

So the question is: is this line of forcing Qt5 causing this bug, or is it the fact that MPL is using Qt5Agg in my environment when I only have Qt6 bindings installed the problem? Am not familiar with Agg backends and what they mean so would love your input

@tacaswell
Copy link
Member

For my environment, mpl.rcParams._get_backend_or_none() returns Qt5Agg, even though I have no Qt5 bindings installed.

This says something in your system is setting the backend to 'qt5agg' which we take to mean "I want to us Qt5" (see #22005 and the linked issuse). Is there something in your environment forcing the backend to 'qt5agg'? Could be an matplotlibrc or a mpl.use in a start up script.

The bug to fix here is that if we are restricting to Qt5 then the error message should not list the Qt6 bindings!

@kwsp
Copy link
Contributor Author

kwsp commented Apr 25, 2023

I doubt its an environment issue, since I can reproduce this problem on 3 separate machines (Windows 10, Windows 11, Ubuntu 22.04) with a clean conda environment. Could you point me to how mpl selects the default backend? I'm curious to see where in code MPL decides to use the Qt5Agg backend by default. I just tested again in a clean conda environment on Windows 10, with just matplotlib and PySide6 installed, and trying %matplotlib qt still gives me that exception. Here's my pip list

(test) PS C:\Users\tnie\code\tmp> pip list
Package                       Version
----------------------------- -------
asttokens                     2.2.1
backcall                      0.2.0
backports.functools-lru-cache 1.6.4
colorama                      0.4.6
contourpy                     1.0.7
cycler                        0.11.0
debugpy                       1.5.1
decorator                     5.1.1
executing                     1.2.0
fonttools                     4.39.3
importlib-metadata            6.6.0
ipykernel                     6.15.0
ipython                       8.12.0
jedi                          0.18.2
jupyter_client                8.2.0
jupyter_core                  5.3.0
kiwisolver                    1.4.4
matplotlib                    3.7.1
matplotlib-inline             0.1.6
nest-asyncio                  1.5.6
numpy                         1.24.3
packaging                     23.1
parso                         0.8.3
pickleshare                   0.7.5
Pillow                        9.5.0
pip                           23.0.1
platformdirs                  3.3.0
prompt-toolkit                3.0.38
psutil                        5.9.0
pure-eval                     0.2.2
Pygments                      2.15.1
pyparsing                     3.0.9
PySide6                       6.5.0
PySide6-Addons                6.5.0
PySide6-Essentials            6.5.0
python-dateutil               2.8.2
pywin32                       305.1
pyzmq                         23.2.0
setuptools                    66.0.0
shiboken6                     6.5.0
six                           1.16.0
stack-data                    0.6.2
tornado                       6.2
traitlets                     5.9.0
typing_extensions             4.5.0
wcwidth                       0.2.6
wheel                         0.38.4
zipp                          3.15.0

@tacaswell
Copy link
Member

I'm curious to see where in code MPL decides to use the Qt5Agg backend by default.

It should not,

if newbackend is rcsetup._auto_backend_sentinel:
current_framework = cbook._get_running_interactive_framework()
mapping = {'qt': 'qtagg',
'gtk3': 'gtk3agg',
'gtk4': 'gtk4agg',
'wx': 'wxagg',
'tk': 'tkagg',
'macosx': 'macosx',
'headless': 'agg'}
best_guess = mapping.get(current_framework, None)
if best_guess is not None:
candidates = [best_guess]
else:
candidates = []
candidates += [
"macosx", "qtagg", "gtk4agg", "gtk3agg", "tkagg", "wxagg"]
# Don't try to fallback on the cairo-based backends as they each have
# an additional dependency (pycairo) over the agg-based backend, and
# are of worse quality.
for candidate in candidates:
try:
switch_backend(candidate)
except ImportError:
continue
else:
rcParamsOrig['backend'] = candidate
return
else:
# Switching to Agg should always succeed; if it doesn't, let the
# exception propagate out.
switch_backend("agg")
rcParamsOrig["backend"] = "agg"
return
is our fallback logic which is why I think it is something else setting the backend to 'qt5agg'.

@kwsp
Copy link
Contributor Author

kwsp commented Apr 26, 2023

Here's are the exact steps to reproduce on Windows 10 and Windows 11.

1. Create a clean Conda environment and install deps

conda create -y -n test python=3.10
conda activate test
# make sure env is actually active and make sure pip comes from this env.
pip install matplotlib ipykernel PySide6  # all packages are pip installed.

2. In VS Code interactive, run the following cells step by step

# %%
import matplotlib.pyplot as plt
import matplotlib as mpl

# %%
mpl.get_backend()  # returns 'module://matplotlib_inline.backend_inline'

# %%
plt.plot(range(10))  # Plots inline OK

# %%
mpl.get_backend()  # Still returns 'module://matplotlib_inline.backend_inline'

# %%
%matplotlib qt
plt.plot(range(10))  # ImportError: Failed to import any of the following Qt binding modules...

# %%
mpl.get_backend()  # returns 'Qt5Agg'

@kwsp
Copy link
Contributor Author

kwsp commented Apr 26, 2023

I'm curious to see where in code MPL decides to use the Qt5Agg backend by default.

It should not,

if newbackend is rcsetup._auto_backend_sentinel:
current_framework = cbook._get_running_interactive_framework()
mapping = {'qt': 'qtagg',
'gtk3': 'gtk3agg',
'gtk4': 'gtk4agg',
'wx': 'wxagg',
'tk': 'tkagg',
'macosx': 'macosx',
'headless': 'agg'}
best_guess = mapping.get(current_framework, None)
if best_guess is not None:
candidates = [best_guess]
else:
candidates = []
candidates += [
"macosx", "qtagg", "gtk4agg", "gtk3agg", "tkagg", "wxagg"]
# Don't try to fallback on the cairo-based backends as they each have
# an additional dependency (pycairo) over the agg-based backend, and
# are of worse quality.
for candidate in candidates:
try:
switch_backend(candidate)
except ImportError:
continue
else:
rcParamsOrig['backend'] = candidate
return
else:
# Switching to Agg should always succeed; if it doesn't, let the
# exception propagate out.
switch_backend("agg")
rcParamsOrig["backend"] = "agg"
return

is our fallback logic which is why I think it is something else setting the backend to 'qt5agg'.

I set a breakpoint on line 234 here, stepped through the code that raised the ImportError, and confirmed this branch was never hit.

@kwsp
Copy link
Contributor Author

kwsp commented Apr 26, 2023

Ok what the hell. The problem isn't with matplotlib, but IPython. I hardcore stepped through the code this time, and it turns out if you do %matplotlib qt, qt always maps to Qt5Agg. So if I only have Qt6 bindings, this would always break because IPython tells matplotlibto use Qt5Agg first, before matplotlib tries to actually import bindings.

https://github.com/ipython/ipython/blob/main/IPython/core/pylabtools.py#L26

https://github.com/ipython/ipython/blob/main/IPython/core/pylabtools.py#L301-L322

@ksunden
Copy link
Member

ksunden commented Apr 26, 2023

(I was about to post the same link, in fact just for posterity, I'll post the permalink rather than the main branch which can change: https://github.com/ipython/ipython/blob/396593e7ad8cab3a9c36fb0f3e26cbf79cff069c/IPython/core/pylabtools.py#L26)

Short term, you should be resolved by doing %matplotlib qt6 instead... longer term, perhaps IPython should update that mapping.

Regardless, going to close as this is not a change we can do, as far as I can tell

@ksunden
Copy link
Member

ksunden commented Apr 26, 2023

Thought a little bit more and opened #25772, which would at least have helped narrow in on this, by not purporting to have tried qt6 when it does not actually do so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants