You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I was twiddling about examining how %gui works, and see that the code effectively forces you into PyQt instead:
In get_inputhook_name_and_func, if 'qt5' or 'qt6' are requested, an environment variable is set forcing you away from PySide<x>. I'm not yet sure what the background is on this QT_API environment variable, but it doesn't seem great to me to set it for the purpose of specifying which library to load (I always assume environment variables with non-specific names could be used by other things in the system, but maybe that's a crazy perspective).
This environment variable will then dictate which Qt package will get imported via qt_for_kernel.py. PySide6 is definitely supported here, but effectively inaccessible via %gui.
(The only missing "support" for PySide6 that I see so far is in the error message in qt_loaders.py.)
I'm happy to work on this (I have some notes on my branch) but would like some guidance from more active members about how this QT_API environment variable should be handled.
Actually, after working on this for ipykernel, I see it's an issue (print statements and updated import error message are from my branch)
In [3]: %gui pyside6
Someone called `enable_gui` with gui='pyside6'.
`get_inputhook_name_and_func` called with gui='pyside6'
ERROR:root:No event loop integration for 'pyside6'. Supported event loops are: qt, qt4, qt5, qt6, gtk, gtk2, gtk3, gtk4, tk, wx, pyglet, glut, osx, asyncio
In [4]: %gui qt6
Someone called `enable_gui` with gui='qt6'.
`get_inputhook_name_and_func` called with gui='qt6'
gui_mod='qt'
`get_options` called with os.environ.get("QT_API", None)='pyqt6'
mpl=None
qt_api='pyqt6'
Importing `IPython.terminal.pt_inputhooks.qt` with api_opts=['pyqt6']
ERROR:root:
Could not load requested Qt binding. Please ensure that
PyQt4 >= 4.7, PyQt5, PyQt6, PySide >= 1.0.3, PySide2, or
PySide6is available, and only one is imported per session.
Currently-imported Qt library: None
PyQt4 available (requires QtCore, QtGui, QtSvg): False
PyQt5 available (requires QtCore, QtGui, QtSvg, QtWidgets): True
PyQt6 available (requires QtCore, QtGui, QtSvg, QtWidgets): False
PySide >= 1.0.3 installed: False
PySide2 installed: False
PySide6 installed: True
Tried to load: ['pyqt6']
In ipykernel, there's logic that prefers PyQtx but settles for PySidex. I think the same thing should happen here, and maybe since ipykernel defers to IPython to import Qt, all the logic should be in one place.
@tacaswell, it seems you're responsible for the logic in Qt import logic in matplotlib (correct me if I'm wrong):
If set, choose the Qt library from the value of QT_API environment variable or whatever Qt library is already imported.
If not set, start trying to import from newest to oldest, preferring PyQt over PySide.
There's an exception to (2) where Qt5 can be forced, and then it will try PyQt5 first, then PySide2.
In matplotlib, specifying "qt" means "get me the newest version"; e.g. matplotlib.use('QtAgg') will import PySide6.
In this pull request in ipykernel, similar logic is implemented; "qt" means latest version, "qt5" means try PyQt5 and then PySide2, etc.. The actual importing is handed off to IPython by setting the QT_API environment variable.
IPython's %gui disagrees; "qt" means "qt4", and there's no logic to interpret a %gui qt6 as "try PyQt6 first, then PySide6". It uses the QT_API environment variable internally, So I suggest moving the logic that sets it from the pull request in pykernel into IPython, so they both behave the same way.
I am not solely responsible, but will take the blame! Your description is correct, but I'm going to re-write it in my own words just to make sure you agree we agree.
so, a bit of history: In the old days Matplotlib had a different backend for each Qt version (backend_qt4agg.py and backend_qt5agg.py) however they were almost identical except for imports and a handful of different lines. When Qt6 came out we re-evaluated and determined that we could support all versions of the Qt bindings via qt_compat and a single backend. Because we did not want to deprecated any of the Qt5 specific modules, we put them back with shims and will remove them when ever support for Qt5 goes away.
Our current logic is as such:
if the user explicitly asks for Qt5 (either via mpl.use('qt5agg') or by importing the qt5 related modules, we will force qt_compat to pick a qt5 binding.
if the user has imported any of the Qt bindings use that one
if we have to pick (because nothing is imported yet)
if Qt bindings are imported we should use them (because we support all 4 and this is better than making the user figure out how to configure us and what ever else is importing Qt bindings)
we do not want to become a general purpose Qt compatibility layer
we can be sure the user always has a chance to do something before we are imported (because the user has to import us)
However most of those do not apply to IPython as it is the shell and runs before the user has a chance to do anything 😞
OK, I'm going to copy the version logic from ipykernel into here, so %gui will do the same thing in both places.
In the future, we can maybe remove the logic in ipykernel and defer to whatever is in ipython so we don't duplicate code. For now, I think we need to have it in two places, or we force a pretty strict dependency.
Activity
shaperilio commentedon Dec 7, 2022
I see mention of
QT_APIin matplotlib.shaperilio commentedon Dec 7, 2022
Never mind; I verified
PySide6is preferred even ifPyQt5is installed.shaperilio commentedon Dec 9, 2022
Actually, after working on this for
ipykernel, I see it's an issue (print statements and updated import error message are from my branch)In
ipykernel, there's logic that prefersPyQtxbut settles forPySidex. I think the same thing should happen here, and maybe sinceipykerneldefers toIPythonto import Qt, all the logic should be in one place.shaperilio commentedon Dec 9, 2022
@tacaswell, it seems you're responsible for the logic in Qt import logic in matplotlib (correct me if I'm wrong):
QT_APIenvironment variable or whatever Qt library is already imported.PyQtoverPySide.There's an exception to (2) where Qt5 can be forced, and then it will try
PyQt5first, thenPySide2.In
matplotlib, specifying "qt" means "get me the newest version"; e.g.matplotlib.use('QtAgg')will importPySide6.In this pull request in ipykernel, similar logic is implemented; "qt" means latest version, "qt5" means try
PyQt5and thenPySide2, etc.. The actual importing is handed off toIPythonby setting theQT_APIenvironment variable.IPython's%guidisagrees; "qt" means "qt4", and there's no logic to interpret a%gui qt6as "tryPyQt6first, thenPySide6". It uses theQT_APIenvironment variable internally, So I suggest moving the logic that sets it from the pull request in pykernel intoIPython, so they both behave the same way.tacaswell commentedon Dec 9, 2022
I am not solely responsible, but will take the blame! Your description is correct, but I'm going to re-write it in my own words just to make sure you agree we agree.
so, a bit of history: In the old days Matplotlib had a different backend for each Qt version (
backend_qt4agg.pyandbackend_qt5agg.py) however they were almost identical except for imports and a handful of different lines. When Qt6 came out we re-evaluated and determined that we could support all versions of the Qt bindings viaqt_compatand a single backend. Because we did not want to deprecated any of the Qt5 specific modules, we put them back with shims and will remove them when ever support for Qt5 goes away.Our current logic is as such:
mpl.use('qt5agg')or by importing the qt5 related modules, we will forceqt_compatto pick a qt5 binding.The logic is:
However most of those do not apply to IPython as it is the shell and runs before the user has a chance to do anything 😞
shaperilio commentedon Dec 12, 2022
OK, I'm going to copy the version logic from ipykernel into here, so
%guiwill do the same thing in both places.In the future, we can maybe remove the logic in
ipykerneland defer to whatever is inipythonso we don't duplicate code. For now, I think we need to have it in two places, or we force a pretty strict dependency.ENH: support for `PySide6` in `%gui` (#13864)