Skip to content
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

Rewrite and greatly simplify qt_compat.py. #9993

Merged
merged 2 commits into from Jul 31, 2018

Conversation

anntzer
Copy link
Contributor

@anntzer anntzer commented Dec 13, 2017

The selection logic is now described in the module's docstring. I think it is essentially unchanged...

The QT_ENV_MAJOR_VERSION global, which would sometimes be defined (depending on the state of the import cache, the QT_API environment variable, and the requested backend) is never defined anymore.

PR Summary

PR Checklist

  • Has Pytest style unit tests
  • Code is PEP 8 compliant
  • New features are documented, with examples if plot related
  • Documentation is sphinx and numpydoc compliant
  • Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
  • Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way

@anntzer anntzer force-pushed the qtcompat branch 3 times, most recently from df8b780 to 333a453 Compare December 13, 2017 09:33
@tacaswell tacaswell added this to the v2.2 milestone Dec 13, 2017
@tacaswell
Copy link
Member

attn @fariza

@tacaswell
Copy link
Member

I am 👍 on this in principle. I gave it a quick read and it seems on the right path but do not have time right now to test with the full matrix of qt5 / qt4 / all the bindings.

Thoughts on using https://pypi.python.org/pypi/QtPy to simply this is well? Picks up an extra dependency, but I think will let us drop a bunch of code and integrate better with other libraries that also import Qt.

@tacaswell
Copy link
Member

spyder-ide/qtpy#84 added pyside2 support xref #6118

@anntzer
Copy link
Contributor Author

anntzer commented Dec 13, 2017

I don't think qtpy really helps, most of the logic in qt_compat is to decide how the already imported modules, the backend/backend.qt4/5 rcParams, the QT_API env var, and the actually importable modules interact to decide what binding to use; AFAICT QtPy only considers the last two points.

QtPy also seems not to support setting the old v1 API for PyQt4 -- not that I really care about it :-) but if we were to drop support for it then qt_compat could be simplified accordingly.

@tacaswell
Copy link
Member

I am happy to merge this as-is (once we get the matrix tested) and punt on qtpy usage.

I think the biggest selling point of switching to qtpy is (possibly) seamless integration with other libraries / tools that use qtpy.

@anntzer
Copy link
Contributor Author

anntzer commented Dec 13, 2017

They can still use qtpy... More specifically, if they import first qtpy and then matplotlib, then mpl will use whatever binding got loaded by qtpy. If they first import matplotlib (or just anything that imports some qt binding first) and then qtpy, qtpy will not check what got loaded and we may end up with two different bindings:

$ QT_API=pyside python -c 'import PyQt5.QtGui; from qtpy import QtWidgets; print(QtWidgets.QApplication)'
<class 'PySide.QtGui.QApplication'>

I would consider this an issue on qtpy's side. Switching to using qtpy ourselves only shifts the issue: in that case someone who first imports, say, PyQt5 but (mis)configured matplotlib to use PySide will run into the same issue as above (but this time we get the blame instead of qtpy).

@anntzer anntzer force-pushed the qtcompat branch 2 times, most recently from 55a9d88 to fdee996 Compare December 13, 2017 22:13
@anntzer
Copy link
Contributor Author

anntzer commented Dec 13, 2017

Also removed qt4agg/qt5agg api docs as their shimming is insufficient with this new qt_compat. Note that the gtkagg api docs are in the same state.
As argued somewhere else I believe the interactive backend docs should be rewritten manually anyways.


# Available APIs.
QT_API_PYQT = 'PyQt4' # API is not set here; Python 2.x default is V 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comments seem useful. I would keep them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added back a comment re: v1 API as v2 is the default on Py3.

@NelleV
Copy link
Member

NelleV commented Jul 14, 2018

@anntzer Do you mind rebasing? There's currently a conflict.

Copy link
Member

@timhoffm timhoffm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to get this forward to untangle #11600.

dict.__setitem__(rcParams, "backend.qt5", QT_API_PYQT5)
elif QT_API_ENV == "pyside2":
dict.__setitem__(rcParams, "backend.qt5", QT_API_PYSIDE2)
QT_API = dict.__getitem__(rcParams, "backend.qt5")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct even if QT_API_ENV == 'foo'?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then we just respect whatever rcParams["backend.qt5"] originally was.

# http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html
_sip_apis = ["QDate", "QDateTime", "QString", "QTextStream", "QTime",
"QUrl", "QVariant"]
import sip
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move import sip into the try-except as done in @tacaswell s commit to #11600.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not clear why this would be needed? The way this is currently written, if QT_API is set then we try that binding exactly (and fail with ImportError if not available, which includes sip not being available when PyQt4 is requested); if not (QT_API is None, which would only occur in a fully manual embedding while rcParams["backend"] is not even "qt4agg" or "qt5agg"), then we just try everything in the loop at the bottom where each iteration is protected by a try... except ImportError.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because riverbank (in the last planned pyqt4 release) made sip a 'private' library such that before you import pyqt import sip fails.

I am a bit unclear how to set the sip api in that case, but...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that was pyqt5?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure what you refer to; I can't repro with PyQt4 installed from conda (it's not available as a pypi wheel) or PyQt5 from either conda or pypi.

else:
raise ImportError("Failed to import any qt binding")
else:
raise AssertionError # We should not get there.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

raise AssertionError("Invalid QT_API: '%s'" % QT_API)

Just in case. We then know at least what the unexpected value was.

def is_pyqt5():
return QT_API == QT_API_PYQT5
# These globals are only defined for backcompatibilty purposes.
ETS = dict(pyqt=(QT_API_PYQTv2, 4), pyside=(QT_API_PYSIDE, 4),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy the comments from the original ETS definition?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dict is basically irrelevant now and only left for backcompat; there's a similar comment at the top ("Otherwise check the QT_API environment variable etc.").

@@ -46,7 +46,7 @@
import warnings

from matplotlib import colors as mcolors
from matplotlib.backends.qt_compat import QtGui, QtWidgets, QtCore
from ..qt_compat import QtCore, QtGui, QtWidgets
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a particular reason to use relative imports. I think to remember that absolute imports are to be preferred in general. but I'm happy to learn. 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because I'd rather have a block of

from .axes import Axes
from .lines import Line2D
...

than additionally repeating matplotlib in front of each line (we have pretty huge import blocks). OTOH this specific change wasn't really needed (I usually just do it in conjunction with other changes) so I reverted it.

@anntzer
Copy link
Contributor Author

anntzer commented Jul 28, 2018

Comments handled (just made the assert more explicit and reverted the change to formlayout).

The selection logic is now described in the module's docstring.  The
only changes is that the QT_ENV_MAJOR_VERSION global, which would
sometimes be defined (depending on the state of the import cache, the
QT_API environment variable, and the requested backend) is never defined
anymore.
Copy link
Member

@timhoffm timhoffm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modulo the sip import. I would leave this for @tacaswell to decide.

Copy link
Member

@tacaswell tacaswell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I approve, but have a commit in this.

@timhoffm
Copy link
Member

@tacaswell I re-approve your commit. Is it ok to merge on this basis?

@tacaswell
Copy link
Member

Yes, @timhoffm you can push the button if you want :)

@timhoffm timhoffm merged commit f677f36 into matplotlib:master Jul 31, 2018
@lumberbot-app
Copy link

lumberbot-app bot commented Jul 31, 2018

There seem to be a conflict, please backport manually

@anntzer anntzer deleted the qtcompat branch July 31, 2018 15:51
tacaswell pushed a commit to tacaswell/matplotlib that referenced this pull request Aug 4, 2018
Rewrite and greatly simplify qt_compat.py.
Conflicts:
        INSTALL.rst
          - kept changes away from App specific wording
          - kept min pyqt4 version bump
	doc/sphinxext/mock_gui_toolkits.py
          - only removed setting up the Qt mocks
	lib/matplotlib/backends/qt_compat.py
          - kept backported version, conflict was in block of
            constants at top (all of the contestants are still
            defined in the module)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
GUI: Qt Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants