Skip to content

Support for PySide6 via %gui #13859

Open
Open

Description

@shaperilio

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.

Activity

  1. shaperilio commented on Dec 7, 2022

    @shaperilio
    ContributorAuthor

    I see mention of QT_API in matplotlib.

  2. shaperilio commented on Dec 7, 2022

    @shaperilio
    ContributorAuthor

    Never mind; I verified PySide6 is preferred even if PyQt5 is installed.

  3. shaperilio commented on Dec 9, 2022

    @shaperilio
    ContributorAuthor

    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.

  4. shaperilio commented on Dec 9, 2022

    @shaperilio
    ContributorAuthor

    @tacaswell, it seems you're responsible for the logic in Qt import logic in matplotlib (correct me if I'm wrong):

    1. If set, choose the Qt library from the value of QT_API environment variable or whatever Qt library is already imported.
    2. 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.

  5. tacaswell commented on Dec 9, 2022

    @tacaswell
    Contributor

    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:

    The logic is:

    • 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 😞

  6. shaperilio commented on Dec 12, 2022

    @shaperilio
    ContributorAuthor

    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.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions