diff --git a/.gitignore b/.gitignore index 6def0a77..9f30b4e8 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,6 @@ lib64 # Other files toread.md .chache +.idea/ + +# End of File diff --git a/appveyor.yml b/appveyor.yml index be164b79..7a878839 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -47,7 +47,7 @@ install: - "powershell ci-helpers/appveyor/install-miniconda.ps1" - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - "activate test" - - "python setup.py install" + - "python setup.py develop" # Not a .NET project, we build in the install step instead build: false diff --git a/ci/test-pyqt4.sh b/ci/test-pyqt4.sh new file mode 100755 index 00000000..cc68554c --- /dev/null +++ b/ci/test-pyqt4.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +export PATH="$HOME/miniconda/bin:$PATH" +source activate test + +# We use container 3 to test with pip and pyqt5 +if [ "$CIRCLE_NODE_INDEX" = "3" ]; then + exit 0 +else + conda remove -q qt pyqt + conda install -q -c conda-forge qt=4.* pyqt=4.* +fi + +python qtpy/tests/runtests.py + +# Force quitting if exit status of runtests.py was not 0 +if [ $? -ne 0 ]; then + exit 1 +fi diff --git a/ci/test-pyqt5.sh b/ci/test-pyqt5.sh new file mode 100755 index 00000000..8f3cad3f --- /dev/null +++ b/ci/test-pyqt5.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +export PATH="$HOME/miniconda/bin:$PATH" +source activate test + +# We use container 3 to test with pip +if [ "$CIRCLE_NODE_INDEX" != "3" ]; then + conda install -q qt=5.* pyqt=5.* +else + pip install -q pyqt5 +fi + +python qtpy/tests/runtests.py + +# Force quitting if exit status of runtests.py was not 0 +if [ $? -ne 0 ]; then + exit 1 +fi diff --git a/ci/test-pyside.sh b/ci/test-pyside.sh new file mode 100755 index 00000000..5aefe1bf --- /dev/null +++ b/ci/test-pyside.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +export PATH="$HOME/miniconda/bin:$PATH" +source activate test + +# We use container 3 to test with pip and pyqt5 +if [ "$CIRCLE_NODE_INDEX" = "3" ]; then + exit 0 +else + conda remove -q qt pyqt + conda install -q -c conda-forge qt=4.* pyside +fi + +python qtpy/tests/runtests.py + +# Force quitting if exit status of runtests.py was not 0 +if [ $? -ne 0 ]; then + exit 1 +fi diff --git a/ci/test-pyside2.sh b/ci/test-pyside2.sh new file mode 100755 index 00000000..82886aef --- /dev/null +++ b/ci/test-pyside2.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +export PATH="$HOME/miniconda/bin:$PATH" +source activate test + +# Download PySide2 wheel +wget -q https://bintray.com/fredrikaverpil/pyside2-wheels/download_file?file_path=ubuntu14.04%2FPySide2-2.0.0.dev0-cp27-none-linux_x86_64.whl -O PySide2-2.0.0.dev0-cp27-none-linux_x86_64.whl + +# We only use container 0 for PySide2 +if [ "$CIRCLE_NODE_INDEX" = "0" ]; then + conda remove -q qt pyqt + pip install PySide2-2.0.0.dev0-cp27-none-linux_x86_64.whl +else + exit 0 +fi + +# Make symlinks for Qt libraries (else imports fail) +pushd "$HOME/miniconda/envs/test/lib/python2.7/site-packages/PySide2/" + +for file in `ls Qt*x86_64-linux-gnu.so` +do + symlink=${file%.x86_64-linux-gnu.so}.so + ln -s $file $symlink +done + +popd + +python qtpy/tests/runtests.py + +# Force quitting if exit status of runtests.py was not 0 +if [ $? -ne 0 ]; then + exit 1 +fi + +pip uninstall -y -q pyside2 diff --git a/circle.yml b/circle.yml index 441f3f3f..1889d8e9 100644 --- a/circle.yml +++ b/circle.yml @@ -4,14 +4,26 @@ machine: # Used by test scripts TEST_CI: "True" # Python versions to test (Maximum of 4 different versions for now) - PY_VERSIONS: "3.5 3.4 2.7" + PY_VERSIONS: "2.7 3.5 3.6 3.5" # For Coveralls COVERALLS_REPO_TOKEN: xh75EzxFFMoTEyNPo3wXxXv8OVkul3eE5 # Used by astropy-ci helpers TRAVIS_OS_NAME: "linux" + CONDA_DEPENDENCIES: "pytest pytest-cov" PIP_DEPENDENCIES: "coveralls" dependencies: + pre: + # We need this for QtMultimedia in 5.8 + - sudo apt-get install libpulse-dev + # For PySide2 + - sudo apt-get autoremove libqt5* + - sudo apt-get install apt -y + - sudo add-apt-repository -y ppa:beineri/opt-qt562-trusty + - sudo apt-get update -y; true + - sudo apt-get install -q qt56webengine + - sudo rsync -a /opt/qt56/ /usr/ + override: # First convert PY_VERSIONS to an array and then select the Python version # based on the CIRCLE_NODE_INDEX @@ -22,25 +34,17 @@ dependencies: source ci-helpers/travis/setup_conda_$TRAVIS_OS_NAME.sh && export PATH="$HOME/miniconda/bin:$PATH" && source activate test && - conda install -q ciocheck -c spyder-ide && python setup.py develop; test: override: - # Check style - - export PATH="$HOME/miniconda/bin:$PATH" && source activate test && ciocheck qtpy: # note the colon + - ./ci/test-pyqt5.sh: # note the colon + parallel: true + - ./ci/test-pyside2.sh: # note the colon parallel: true - # Check PyQt5 - - export PATH="$HOME/miniconda/bin:$PATH" && source activate test && conda install -q qt=5.* pyqt=5.* && python qtpy/tests/runtests.py: # note the colon + - ./ci/test-pyqt4.sh: # note the colon parallel: true - # Check PySide2 -# - export PATH="$HOME/miniconda/bin:$PATH" && source activate test && conda install -q qt=5.* pyside2 && python qtpy/tests/runtests.py: # note the colon -# parallel: true - # Check PyQt4 - - export PATH="$HOME/miniconda/bin:$PATH" && source activate test && conda install -q qt=4.* pyqt=4.* && python qtpy/tests/runtests.py: # note the colon + - ./ci/test-pyside.sh: # note the colon parallel: true - export PATH="$HOME/miniconda/bin:$PATH" && source activate test && coveralls: # note the colon parallel: true - # Check Pyside - #- export PATH="$HOME/miniconda/bin:$PATH" && source activate test && conda remove -q qt pyqt && conda install -c conda-forge qt=4.* pyside && python qtpy/tests/runtests.py: # note the colon - # parallel: true diff --git a/qtpy/QtCore.py b/qtpy/QtCore.py index 6794cb45..25702420 100644 --- a/qtpy/QtCore.py +++ b/qtpy/QtCore.py @@ -10,7 +10,7 @@ Provides QtCore classes and functions. """ -from . import PYQT5, PYQT4, PYSIDE, PythonQtError +from . import PYQT5, PYSIDE2, PYQT4, PYSIDE, PythonQtError if PYQT5: @@ -22,6 +22,8 @@ # Those are imported from `import *` del pyqtSignal, pyqtSlot, pyqtProperty, QT_VERSION_STR +elif PYSIDE2: + from PySide2.QtCore import * elif PYQT4: from PyQt4.QtCore import * # Those are things we inherited from Spyder that fix crazy crashes under diff --git a/qtpy/QtGui.py b/qtpy/QtGui.py index 515c081c..99ea6f37 100644 --- a/qtpy/QtGui.py +++ b/qtpy/QtGui.py @@ -13,11 +13,13 @@ the ``PyQt5.QtGui`` module. """ -from . import PYQT5, PYQT4, PYSIDE, PythonQtError +from . import PYQT5, PYQT4, PYSIDE, PYSIDE2, PythonQtError if PYQT5: from PyQt5.QtGui import * +elif PYSIDE2: + from PySide2.QtGui import * elif PYQT4: try: # Older versions of PyQt4 do not provide these diff --git a/qtpy/QtMultimedia.py b/qtpy/QtMultimedia.py index 7ed307e8..a20cc3bd 100644 --- a/qtpy/QtMultimedia.py +++ b/qtpy/QtMultimedia.py @@ -1,10 +1,15 @@ from . import PYQT5 from . import PYQT4 from . import PYSIDE +from . import PYSIDE2 if PYQT5: from PyQt5.QtMultimedia import * +elif PYSIDE2: + # Current wheels don't have this module + # from PySide2.QtMultimedia import * + pass elif PYQT4: from PyQt4.QtMultimedia import * from PyQt4.QtGui import QSound diff --git a/qtpy/QtNetwork.py b/qtpy/QtNetwork.py index de4ff473..49faded7 100644 --- a/qtpy/QtNetwork.py +++ b/qtpy/QtNetwork.py @@ -10,11 +10,13 @@ Provides QtNetwork classes and functions. """ -from . import PYQT5, PYQT4, PYSIDE, PythonQtError +from . import PYQT5, PYSIDE2, PYQT4, PYSIDE, PythonQtError if PYQT5: from PyQt5.QtNetwork import * +elif PYSIDE2: + from PySide2.QtNetwork import * elif PYQT4: from PyQt4.QtNetwork import * elif PYSIDE: diff --git a/qtpy/QtPrintSupport.py b/qtpy/QtPrintSupport.py index 8959fad9..b821d411 100644 --- a/qtpy/QtPrintSupport.py +++ b/qtpy/QtPrintSupport.py @@ -9,11 +9,13 @@ Provides QtPrintSupport classes and functions. """ -from . import PYQT5, PYQT4, PYSIDE, PythonQtError +from . import PYQT5, PYQT4,PYSIDE2, PYSIDE, PythonQtError if PYQT5: from PyQt5.QtPrintSupport import * +elif PYSIDE2: + from PySide2.QtPrintSupport import * elif PYQT4: from PyQt4.QtGui import (QAbstractPrintDialog, QPageSetupDialog, QPrintDialog, QPrintEngine, QPrintPreviewDialog, diff --git a/qtpy/QtSvg.py b/qtpy/QtSvg.py index 0bc73205..edc075ea 100644 --- a/qtpy/QtSvg.py +++ b/qtpy/QtSvg.py @@ -8,10 +8,12 @@ """Provides QtSvg classes and functions.""" # Local imports -from . import PYQT4, PYQT5, PYSIDE, PythonQtError +from . import PYQT4, PYSIDE2, PYQT5, PYSIDE, PythonQtError if PYQT5: from PyQt5.QtSvg import * +elif PYSIDE2: + from PySide2.QtSvg import * elif PYQT4: from PyQt4.QtSvg import * elif PYSIDE: @@ -19,4 +21,4 @@ else: raise PythonQtError('No Qt bindings could be found') -del PYQT4, PYQT5, PYSIDE +del PYQT4, PYQT5, PYSIDE, PYSIDE2 diff --git a/qtpy/QtTest.py b/qtpy/QtTest.py index 28067d8b..cca5e192 100644 --- a/qtpy/QtTest.py +++ b/qtpy/QtTest.py @@ -10,11 +10,13 @@ Provides QtTest and functions """ -from . import PYQT5, PYQT4, PYSIDE, PythonQtError +from . import PYQT5,PYSIDE2, PYQT4, PYSIDE, PythonQtError if PYQT5: from PyQt5.QtTest import QTest +elif PYSIDE2: + from PySide2.QtTest import QTest elif PYQT4: from PyQt4.QtTest import QTest as OldQTest diff --git a/qtpy/QtWebEngineWidgets.py b/qtpy/QtWebEngineWidgets.py index 18b5e58f..c5577a22 100644 --- a/qtpy/QtWebEngineWidgets.py +++ b/qtpy/QtWebEngineWidgets.py @@ -10,7 +10,7 @@ Provides QtWebEngineWidgets classes and functions. """ -from . import PYQT5, PYQT4, PYSIDE, PythonQtError +from . import PYQT5,PYSIDE2, PYQT4, PYSIDE, PythonQtError # To test if we are using WebEngine or WebKit @@ -27,6 +27,18 @@ from PyQt5.QtWebKitWidgets import QWebView as QWebEngineView from PyQt5.QtWebKit import QWebSettings as QWebEngineSettings WEBENGINE = False +elif PYSIDE2: + try: + from PySide2.QtWebEngineWidgets import QWebEnginePage + from PySide2.QtWebEngineWidgets import QWebEngineView + # Current PySide2 wheels seem to be missing this. + # from PySide2.QtWebEngineWidgets import QWebEngineSettings + except ImportError: + from PySide2.QtWebKitWidgets import QWebPage as QWebEnginePage + from PySide2.QtWebKitWidgets import QWebView as QWebEngineView + # Current PySide2 wheels seem to be missing this. + # from PySide2.QtWebKit import QWebSettings as QWebEngineSettings + WEBENGINE = False elif PYQT4: from PyQt4.QtWebKit import QWebPage as QWebEnginePage from PyQt4.QtWebKit import QWebView as QWebEngineView diff --git a/qtpy/QtWidgets.py b/qtpy/QtWidgets.py index 67885168..739f9ce1 100644 --- a/qtpy/QtWidgets.py +++ b/qtpy/QtWidgets.py @@ -13,13 +13,15 @@ were the ``PyQt5.QtWidgets`` module. """ -from . import PYQT5, PYQT4, PYSIDE, PythonQtError +from . import PYQT5, PYSIDE2, PYQT4, PYSIDE, PythonQtError from ._patch.qcombobox import patch_qcombobox from ._patch.qheaderview import introduce_renamed_methods_qheaderview if PYQT5: from PyQt5.QtWidgets import * +elif PYSIDE2: + from PySide2.QtWidgets import * elif PYQT4: from PyQt4.QtGui import * QStyleOptionViewItem = QStyleOptionViewItemV4 diff --git a/qtpy/__init__.py b/qtpy/__init__.py index 273df1d3..1ade9a8c 100644 --- a/qtpy/__init__.py +++ b/qtpy/__init__.py @@ -26,6 +26,17 @@ >>> print(QtWidgets.QWidget) +PySide2 +====== + +Set the QT_API environment variable to 'pyside2' before importing other +packages:: + + >>> import os + >>> os.environ['QT_API'] = 'pyside2' + >>> from qtpy import QtGui, QtWidgets, QtCore + >>> print(QtWidgets.QWidget) + PyQt4 ===== @@ -55,25 +66,32 @@ # Version of QtPy from ._version import __version__ -#: Qt API environment variable name +# Qt API environment variable name QT_API = 'QT_API' -#: names of the expected PyQt5 api + +# Names of the expected PyQt5 api PYQT5_API = ['pyqt5'] -#: names of the expected PyQt4 api + +# Names of the expected PyQt4 api PYQT4_API = [ 'pyqt', # name used in IPython.qt 'pyqt4' # pyqode.qt original name ] -#: names of the expected PySide api + +# Names of the expected PySide api PYSIDE_API = ['pyside'] +# Names of the expected PySide2 api +PYSIDE2_API = ['pyside2'] + +# Setting a default value for QT_API os.environ.setdefault(QT_API, 'pyqt5') API = os.environ[QT_API].lower() -assert API in (PYQT5_API + PYQT4_API + PYSIDE_API) +assert API in (PYQT5_API + PYQT4_API + PYSIDE_API + PYSIDE2_API) is_old_pyqt = is_pyqt46 = False PYQT5 = True -PYQT4 = PYSIDE = False +PYQT4 = PYSIDE = PYSIDE2 = False class PythonQtError(Exception): @@ -86,6 +104,17 @@ class PythonQtError(Exception): from PyQt5.Qt import PYQT_VERSION_STR as PYQT_VERSION # analysis:ignore from PyQt5.Qt import QT_VERSION_STR as QT_VERSION # analysis:ignore PYSIDE_VERSION = None + except ImportError: + API = os.environ['QT_API'] = 'pyside2' + +if API in PYSIDE2_API: + try: + from PySide2 import __version__ as PYSIDE_VERSION # analysis:ignore + from PySide2.QtCore import __version__ as QT_VERSION # analysis:ignore + + PYQT_VERSION = None + PYQT5 = False + PYSIDE2 = True except ImportError: API = os.environ['QT_API'] = 'pyqt' @@ -119,13 +148,14 @@ class PythonQtError(Exception): from PySide import __version__ as PYSIDE_VERSION # analysis:ignore from PySide.QtCore import __version__ as QT_VERSION # analysis:ignore PYQT_VERSION = None - PYQT5 = False + PYQT5 = PYSIDE2 = False PYSIDE = True except ImportError: raise PythonQtError('No Qt bindings could be found') API_NAME = {'pyqt5': 'PyQt5', 'pyqt': 'PyQt4', 'pyqt4': 'PyQt4', - 'pyside': 'PySide'}[API] + 'pyside': 'PySide', 'pyside2':'PySide2'}[API] + if PYQT4: import sip try: diff --git a/qtpy/tests/__init__.py b/qtpy/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/qtpy/tests/conftest.py b/qtpy/tests/conftest.py index c31a78aa..c631886f 100644 --- a/qtpy/tests/conftest.py +++ b/qtpy/tests/conftest.py @@ -54,6 +54,18 @@ def pytest_report_header(config): except AttributeError: versions += 'unknown version' + versions += os.linesep + versions += 'PySide2: ' + + try: + import PySide2 + from PySide2 import QtCore + versions += "PySide: {0} - Qt: {1}".format(PySide2.__version__, QtCore.__version__) + except ImportError: + versions += 'not installed' + except AttributeError: + versions += 'unknown version' + versions += os.linesep return versions diff --git a/qtpy/tests/test_main.py b/qtpy/tests/test_main.py index 63d1439e..2449249c 100644 --- a/qtpy/tests/test_main.py +++ b/qtpy/tests/test_main.py @@ -13,6 +13,15 @@ def assert_pyside(): assert QtWidgets.QWidget is PySide.QtGui.QWidget assert QtWebEngineWidgets.QWebEnginePage is PySide.QtWebKit.QWebPage +def assert_pyside2(): + """ + Make sure that we are using PySide + """ + import PySide2 + assert QtCore.QEvent is PySide2.QtCore.QEvent + assert QtGui.QPainter is PySide2.QtGui.QPainter + assert QtWidgets.QWidget is PySide2.QtWidgets.QWidget + assert QtWebEngineWidgets.QWebEnginePage is PySide2.QtWebEngineWidgets.QWebEnginePage def assert_pyqt4(): """ @@ -52,6 +61,8 @@ def test_qt_api(): assert_pyqt4() elif QT_API == 'pyqt5': assert_pyqt5() + elif QT_API == 'pyside2': + assert_pyside2() else: # If the tests are run locally, USE_QT_API and QT_API may not be # defined, but we still want to make sure qtpy is behaving sensibly. diff --git a/qtpy/tests/test_patch_qcombobox.py b/qtpy/tests/test_patch_qcombobox.py index 04a83370..2e5e6fe3 100644 --- a/qtpy/tests/test_patch_qcombobox.py +++ b/qtpy/tests/test_patch_qcombobox.py @@ -1,6 +1,12 @@ from __future__ import absolute_import -from qtpy import QtGui, QtWidgets +import sys + +import pytest +from qtpy import PYSIDE2, QtGui, QtWidgets + + +PY3 = sys.version[0] == "3" def get_qapp(icon_path=None): @@ -19,6 +25,7 @@ def __getitem__(self, item): raise ValueError("Failing") +@pytest.mark.skipif(PY3 or PYSIDE2, reason="It segfaults in Python 3 and PYSIDE2") def test_patched_qcombobox(): """ In PySide, using Python objects as userData in QComboBox causes diff --git a/qtpy/tests/test_patch_qheaderview.py b/qtpy/tests/test_patch_qheaderview.py index 6f30c337..6c9e6ded 100644 --- a/qtpy/tests/test_patch_qheaderview.py +++ b/qtpy/tests/test_patch_qheaderview.py @@ -1,12 +1,17 @@ from __future__ import absolute_import -from qtpy import PYSIDE, PYQT4 +import sys + +import pytest +from qtpy import PYSIDE, PYSIDE2, PYQT4 from qtpy.QtWidgets import QApplication from qtpy.QtWidgets import QHeaderView from qtpy.QtCore import Qt from qtpy.QtCore import QAbstractListModel -import pytest + +PY3 = sys.version[0] == "3" + def get_qapp(icon_path=None): qapp = QApplication.instance() @@ -14,6 +19,8 @@ def get_qapp(icon_path=None): qapp = QApplication(['']) return qapp + +@pytest.mark.skipif(PY3 or PYSIDE2, reason="It fails on Python 3 and PySide2") def test_patched_qheaderview(): """ This will test whether QHeaderView has the new methods introduced in Qt5. diff --git a/qtpy/tests/test_qtmultimedia.py b/qtpy/tests/test_qtmultimedia.py index a718d188..5561e77b 100644 --- a/qtpy/tests/test_qtmultimedia.py +++ b/qtpy/tests/test_qtmultimedia.py @@ -1,8 +1,10 @@ from __future__ import absolute_import -from qtpy import QtMultimedia +import pytest +from qtpy import PYSIDE2, QtMultimedia +@pytest.mark.skipif(PYSIDE2, reason="It fails on PySide2") def test_qtmultimedia(): """Test the qtpy.QtMultimedia namespace""" assert QtMultimedia.QAbstractVideoBuffer is not None diff --git a/qtpy/tests/test_uic.py b/qtpy/tests/test_uic.py index 6e732bb2..1c50e9fe 100644 --- a/qtpy/tests/test_uic.py +++ b/qtpy/tests/test_uic.py @@ -2,7 +2,8 @@ import sys import contextlib -from qtpy import QtWidgets +import pytest +from qtpy import PYSIDE2, QtWidgets from qtpy.QtWidgets import QComboBox from qtpy import uic from qtpy.uic import loadUi @@ -69,6 +70,7 @@ def test_load_ui_custom_auto(tmpdir): assert isinstance(ui.comboBox, _QComboBoxSubclass) +@pytest.mark.skipif(PYSIDE2, reason="It fails on PySide2") def test_load_full_uic(): """Test that we load the full uic objects for PyQt5 and PyQt4.""" QT_API = os.environ.get('QT_API', '').lower() diff --git a/qtpy/uic.py b/qtpy/uic.py index b3d9324d..07d7a787 100644 --- a/qtpy/uic.py +++ b/qtpy/uic.py @@ -1,6 +1,6 @@ import os -from . import PYSIDE, PYQT4, PYQT5 +from . import PYSIDE, PYSIDE2, PYQT4, PYQT5 from .QtWidgets import QComboBox @@ -12,7 +12,7 @@ from PyQt4.uic import * -elif PYSIDE: +else: __all__ = ['loadUi'] @@ -78,8 +78,12 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. - from PySide.QtCore import QMetaObject - from PySide.QtUiTools import QUiLoader + if PYSIDE: + from PySide.QtCore import QMetaObject + from PySide.QtUiTools import QUiLoader + elif PYSIDE2: + from PySide2.QtCore import QMetaObject + from PySide2.QtUiTools import QUiLoader class UiLoader(QUiLoader): """