Skip to content

Commit

Permalink
PR: Test using PyQt extra packages (#446)
Browse files Browse the repository at this point in the history
  • Loading branch information
ccordoba12 authored Aug 28, 2023
2 parents 354461c + f81a0b3 commit d7ff724
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 57 deletions.
41 changes: 36 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ jobs:
PYSIDE6_VERSION: ${{ matrix.pyside6-version || matrix.qt6-version-default }}
PYSIDE6_QT_VERSION: ${{ matrix.pyside6-qt-version || matrix.pyside6-version || matrix.qt6-version-default }}
QSCINTILLA_VERSION: ${{ matrix.qscintilla-version || matrix.qscintilla-version-default }}
PYQT_EXTRAS: ${{ matrix.pyqt-extras || matrix.pyqt-extras-default }}
SKIP_PIP_CHECK: ${{ matrix.skip-pip-check }}
strategy:
fail-fast: false
Expand All @@ -48,31 +49,34 @@ jobs:
qt5-version-default: ['5.12']
qt6-version-default: ['6.3']
qscintilla-version-default: ['2.13']
pyqt-extras-default: ['No']
include:
- os: ubuntu-latest
special-invocation: 'xvfb-run --auto-servernum ' # Needed for GUI tests to work
- python-version: '3.11'
pyqt5-version: '5.15' # Python 3.11 needs 5.15+
pyside2-version: '5.15' # Python 3.11 needs 5.15+
pyside6-version: '6.4' # Python 3.11 needs 6.4+
pyside6-version: '6.5' # Python 3.11 needs 6.4+. Test upper bound
- use-conda: 'Yes'
skip-pyqt6: true # No PyQt6 conda packages yet
pyside6-version: '6.4' # Conda only has 6.4+ for Python <3.8
- use-conda: 'No'
pyqt5-version: '5.15' # Test with latest optional packages
- python-version: '3.7'
use-conda: 'Yes'
pyside2-version: '5.13' # Conda needs 5.13+ to work reliably
pyside2-qt-version: '5.12' # Conda only has 5.12 and 5.15, not 5.13
pyside6-version: '6.4' # Conda only has 6.4 for Python <3.8
- python-version: '3.11'
use-conda: 'No'
pyqt-extras: 'Yes' # Check PyQt extras
skip-pyside2: true # Pyside2 wheels don't support Python 3.11+
pyqt6-version: '6.5' # Test upper bound
pyside6-version: '6.5' # Test upper bound
- os: windows-latest
python-version: '3.7'
use-conda: 'Yes'
pyqt5-version: '5.9' # Test lower bound
skip-pyside6: true # Test hangs
skip-pyside6: true # Test hangs with 6.4. 6.5 is not available for Python 3.7
- os: windows-latest
python-version: '3.7'
use-conda: 'No'
Expand All @@ -81,13 +85,40 @@ jobs:
- os: windows-latest
python-version: '3.11'
use-conda: 'Yes'
skip-pyside6: true # Test hangs
pyside6-version: 6.5 # Test upper bound
- os: macos-latest
python-version: '3.7'
python-version: '3.11'
use-conda: 'No'
pyqt6-version: 6.5 # Test upper bound
pyside2-version: 5.15 # Test upper bound
steps:
- name: Check job values
run: |
echo "---- General setup"
echo "OS:" ${{ matrix.os }}
echo "PYTHON_VERSION:" ${{ env.PYTHON_VERSION }}
echo "USE_CONDA:" ${{ env.USE_CONDA }}
echo "---- PyQt"
echo "PYQT_EXTRAS:" ${{ env.PYQT_EXTRAS }}
echo "---- PyQt5"
echo "SKIP_PYQT5:" ${{ matrix.skip-pyqt5 }}
echo "PYQT5_VERSION:" ${{ env.PYQT5_VERSION }}
echo "PYQT5_QT_VERSION:" ${{ env.PYQT5_QT_VERSION }}
echo "QSCINTILLA_VERSION:" ${{ env.QSCINTILLA_VERSION }}
echo "---- PyQt6"
echo "SKIP_PYQT6:" ${{ matrix.skip-pyqt6 }}
echo "PYQT6_VERSION:" ${{ env.PYQT6_VERSION }}
echo "PYQT6_QT_VERSION:" ${{ env.PYQT6_QT_VERSION }}
echo "---- PySide2"
echo "SKIP_PYSIDE2:" ${{ matrix.skip-pyside2 }}
echo "PYSIDE2_VERSION:" ${{ env.PYSIDE2_VERSION }}
echo "PYSIDE2_QT_VERSION:" ${{ env.PYSIDE2_QT_VERSION }}
echo "---- PySide6"
echo "SKIP_PYSIDE6:" ${{ matrix.skip-pyside6 }}
echo "PYSIDE6_VERSION:" ${{ env.PYSIDE6_VERSION }}
echo "PYSIDE6_QT_VERSION:" ${{ env.PYSIDE6_QT_VERSION }}
echo "---- Other"
echo "SKIP_PIP_CHECK:" ${{ env.SKIP_PIP_CHECK }}
- name: Checkout branch
uses: actions/checkout@v3
- name: Setup Python
Expand Down
34 changes: 32 additions & 2 deletions .github/workflows/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,39 @@ conda activate test-env-${BINDING}
if [ "$USE_CONDA" = "No" ]; then

if [ "${1}" = "pyqt5" ]; then
pip install pyqt5==${PYQT5_VERSION}.* PyQtWebEngine==${PYQT5_VERSION}.* QScintilla==${QSCINTILLA_VERSION}.*

if [ "$PYQT_EXTRAS" = "Yes" ]; then
pip install pyqt5==${PYQT5_VERSION}.* \
PyQtWebEngine==${PYQT5_VERSION}.* \
QScintilla==${QSCINTILLA_VERSION}.* \
PyQt3D==${PYQT5_VERSION}.* \
PyQtChart==${PYQT5_VERSION}.* \
PyQtDataVisualization==${PYQT5_VERSION}.* \
PyQtNetworkAuth==${PYQT5_VERSION}.* \
PyQtPurchasing==${PYQT5_VERSION}.*
else
pip install pyqt5==${PYQT5_VERSION}.* \
PyQtWebEngine==${PYQT5_VERSION}.* \
QScintilla==${QSCINTILLA_VERSION}.*
fi

elif [ "${1}" = "pyqt6" ]; then
pip install pyqt6==${PYQT6_VERSION}.* PyQt6-WebEngine==${PYQT6_VERSION}.* PyQt6-Qt6==${PYQT6_QT_VERSION}.* PyQt6-QScintilla==${QSCINTILLA_VERSION}.*

if [ "$PYQT_EXTRAS" = "Yes" ]; then
pip install pyqt6==${PYQT6_VERSION}.* \
PyQt6-WebEngine==${PYQT6_VERSION}.* \
PyQt6-Qt6==${PYQT6_QT_VERSION}.* \
PyQt6-QScintilla \
PyQt6-3D==${PYQT6_VERSION}.* \
PyQt6-Charts==${PYQT6_VERSION}.* \
PyQt6-DataVisualization==${PYQT6_VERSION}.* \
PyQt6-NetworkAuth==${PYQT6_VERSION}.*
else
pip install pyqt6==${PYQT6_VERSION}.* \
PyQt6-WebEngine==${PYQT6_VERSION}.* \
PyQt6-Qt6==${PYQT6_QT_VERSION}.*
fi

elif [ "${1}" = "pyside2" ]; then
pip install pyside2==${PYSIDE2_VERSION}.*
elif [ "${1}" = "pyside6" ]; then
Expand Down
74 changes: 64 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@
[![Github build status](https://github.com/spyder-ide/qtpy/workflows/Tests/badge.svg)](https://github.com/spyder-ide/qtpy/actions)
[![Coverage Status](https://coveralls.io/repos/github/spyder-ide/qtpy/badge.svg?branch=master)](https://coveralls.io/github/spyder-ide/qtpy?branch=master)

*Copyright © 2009–2022 The Spyder Development Team*
*Copyright © 2009– The Spyder Development Team*


## Description

**QtPy** is a small abstraction layer that lets you
write applications using a single API call to either PyQt or PySide.

It provides support for PyQt5, PyQt6, PySide6, PySide2 using the Qt5 layout
It provides support for PyQt5, PySide2, PyQt6 and PySide6 using the Qt5 layout
(where the QtGui module has been split into QtGui and QtWidgets).

Basically, you can write your code as if you were using PyQt or PySide directly,
Expand All @@ -41,7 +41,7 @@ to a particular project or namespace.

### License

This project is released under the MIT license.
This project is released under the [MIT license](LICENSE.txt).


### Requirements
Expand Down Expand Up @@ -107,10 +107,12 @@ conda install qtpy
Type checkers have no knowledge of installed packages, so these tools require
additional configuration.

A Command Line Interface (CLI) is offered to help with usage of QtPy (to get MyPy
and Pyright/Pylance args/configurations).

#### Mypy

A Command Line Interface (CLI) is offered to help with usage of QtPy.
Presently, its only feature is to generate command line arguments for Mypy
The `mypy-args` command helps you to generate command line arguments for Mypy
that will enable it to process the QtPy source files with the same API
as QtPy itself would have selected.

Expand Down Expand Up @@ -139,8 +141,11 @@ mypy --package mypackage $(qtpy mypy-args)

#### Pyright/Pylance

Instead of runtime arguments, it is required to create a config file for the project,
called `pyrightconfig.json` or a `pyright` section in `pyproject.toml`. See [here](https://github.com/microsoft/pyright/blob/main/docs/configuration.md) for reference.
In the case of Pyright, instead of runtime arguments, it is required to create a
config file for the project, called `pyrightconfig.json` or a `pyright` section
in `pyproject.toml`. See [here](https://github.com/microsoft/pyright/blob/main/docs/configuration.md)
for reference. In order to set this configuration, QtPy offers the `pyright-config`
command for guidance.

If you run

Expand All @@ -149,14 +154,63 @@ qtpy pyright-config
```

you will get the necessary configs to be included in your project files. If you don't
have them, it is recommended to create the latter.
have them, it is recommended to create the latter. For example, in an environment where PyQt5
is installed and selected (or the default fallback, if no binding can be found in the
environment), this would output the following:

```text
pyrightconfig.json:
{"defineConstant": {"PYQT5": true, "PYSIDE2": false, "PYQT6": false, "PYSIDE6": false}}
pyproject.toml:
[tool.pyright.defineConstant]
PYQT5 = true
PYSIDE2 = false
PYQT6 = false
PYSIDE6 = false
```

**Note**: These configurations are necessary for the correct usage of the default VSCode's type
checking feature while using QtPy in your source code.


## Testing matrix

Currently, QtPy runs tests for different bindings on Linux, Windows and macOS, using
Python 3.7 and 3.11, and installing those bindings with `conda` and `pip`. For the
PyQt bindings, we also check the installation of extra packages via `pip`.

Following this, the current test matrix looks something like this:

| | Python | 3.7 | | 3.11 | |
|---------|-----------------|--------------------------------------------|------|--------------------|----------------------------|
| OS | Binding / manager | conda | pip | conda | pip |
| Linux | PyQt5 | 5.12 | 5.15 | 5.15 | 5.15 (with extras) |
| | PyQt6 | skip (unavailable) | 6.3 | skip (unavailable) | 6.5 (with extras) |
| | PySide2 | 5.13 | 5.12 | 5.15 | skip (no wheels available) |
| | PySide6 | 6.4 | 6.3 | 6.5 | 6.5 |
| Windows | PyQt5 | 5.9 | 5.15 | 5.15 | 5.15 (with extras) |
| | PyQt6 | skip (unavailable) | 6.2 | skip (unavailable) | 6.5 (with extras) |
| | PySide2 | 5.13 | 5.12 | 5.15 | skip (no wheels available) |
| | PySide6 | skip (test hang with 6.4. 6.5 unavailable) | 6.2 | 6.5 | 6.5 |
| MacOS | PyQt5 | 5.12 | 5.15 | 5.15 | 5.15 (with extras) |
| | PyQt6 | skip (unavailable) | 6.3 | skip (unavailable) | 6.5 (with extras) |
| | PySide2 | 5.13 | 5.12 | 5.15 | skip (no wheels available) |
| | PySide6 | 6.4 | 6.3 | 6.5 | 6.5 |

These steps are necessary for running the default VSCode's type checking.
**Note**: The mentioned extra packages for the PyQt bindings are the following:

* `PyQt3D` and `PyQt6-3D`
* `PyQtChart` and `PyQt6-Charts`
* `PyQtDataVisualization` and `PyQt6-DataVisualization`
* `PyQtNetworkAuth` and `PyQt6-NetworkAuth`
* `PyQtPurchasing`
* `PyQtWebEngine` and `PyQt6-WebEngine`
* `QScintilla` and `PyQt6-QScintilla`

## Contributing

Everyone is welcome to contribute!
Everyone is welcome to contribute! See our [Contributing guide](CONTRIBUTING.md) for more details.


## Sponsors
Expand Down
73 changes: 37 additions & 36 deletions qtpy/tests/test_qtdatavisualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,40 +46,41 @@ def test_qtdatavisualization():
assert QtDataVisualization.QLogValue3DAxisFormatter is not None

# QtDatavisualization
QtDatavisualization = pytest.importorskip("qtpy.QtDatavisualization")
# import qtpy to get alias for `QtDataVisualization` with lower `v`
qtpy = pytest.importorskip("qtpy")

assert QtDatavisualization.QScatter3DSeries is not None
assert QtDatavisualization.QSurfaceDataItem is not None
assert QtDatavisualization.QSurface3DSeries is not None
assert QtDatavisualization.QAbstract3DInputHandler is not None
assert QtDatavisualization.QHeightMapSurfaceDataProxy is not None
assert QtDatavisualization.QAbstractDataProxy is not None
assert QtDatavisualization.Q3DCamera is not None
assert QtDatavisualization.QAbstract3DGraph is not None
assert QtDatavisualization.QCustom3DVolume is not None
assert QtDatavisualization.Q3DInputHandler is not None
assert QtDatavisualization.QBarDataProxy is not None
assert QtDatavisualization.QSurfaceDataProxy is not None
assert QtDatavisualization.QScatterDataItem is not None
assert QtDatavisualization.Q3DLight is not None
assert QtDatavisualization.QScatterDataProxy is not None
assert QtDatavisualization.QValue3DAxis is not None
assert QtDatavisualization.Q3DBars is not None
assert QtDatavisualization.QBarDataItem is not None
assert QtDatavisualization.QItemModelBarDataProxy is not None
assert QtDatavisualization.Q3DTheme is not None
assert QtDatavisualization.QCustom3DItem is not None
assert QtDatavisualization.QItemModelScatterDataProxy is not None
assert QtDatavisualization.QValue3DAxisFormatter is not None
assert QtDatavisualization.QItemModelSurfaceDataProxy is not None
assert QtDatavisualization.Q3DScatter is not None
assert QtDatavisualization.QTouch3DInputHandler is not None
assert QtDatavisualization.QBar3DSeries is not None
assert QtDatavisualization.QAbstract3DAxis is not None
assert QtDatavisualization.Q3DScene is not None
assert QtDatavisualization.QCategory3DAxis is not None
assert QtDatavisualization.QAbstract3DSeries is not None
assert QtDatavisualization.Q3DObject is not None
assert QtDatavisualization.QCustom3DLabel is not None
assert QtDatavisualization.Q3DSurface is not None
assert QtDatavisualization.QLogValue3DAxisFormatter is not None
assert qtpy.QtDatavisualization.QScatter3DSeries is not None
assert qtpy.QtDatavisualization.QSurfaceDataItem is not None
assert qtpy.QtDatavisualization.QSurface3DSeries is not None
assert qtpy.QtDatavisualization.QAbstract3DInputHandler is not None
assert qtpy.QtDatavisualization.QHeightMapSurfaceDataProxy is not None
assert qtpy.QtDatavisualization.QAbstractDataProxy is not None
assert qtpy.QtDatavisualization.Q3DCamera is not None
assert qtpy.QtDatavisualization.QAbstract3DGraph is not None
assert qtpy.QtDatavisualization.QCustom3DVolume is not None
assert qtpy.QtDatavisualization.Q3DInputHandler is not None
assert qtpy.QtDatavisualization.QBarDataProxy is not None
assert qtpy.QtDatavisualization.QSurfaceDataProxy is not None
assert qtpy.QtDatavisualization.QScatterDataItem is not None
assert qtpy.QtDatavisualization.Q3DLight is not None
assert qtpy.QtDatavisualization.QScatterDataProxy is not None
assert qtpy.QtDatavisualization.QValue3DAxis is not None
assert qtpy.QtDatavisualization.Q3DBars is not None
assert qtpy.QtDatavisualization.QBarDataItem is not None
assert qtpy.QtDatavisualization.QItemModelBarDataProxy is not None
assert qtpy.QtDatavisualization.Q3DTheme is not None
assert qtpy.QtDatavisualization.QCustom3DItem is not None
assert qtpy.QtDatavisualization.QItemModelScatterDataProxy is not None
assert qtpy.QtDatavisualization.QValue3DAxisFormatter is not None
assert qtpy.QtDatavisualization.QItemModelSurfaceDataProxy is not None
assert qtpy.QtDatavisualization.Q3DScatter is not None
assert qtpy.QtDatavisualization.QTouch3DInputHandler is not None
assert qtpy.QtDatavisualization.QBar3DSeries is not None
assert qtpy.QtDatavisualization.QAbstract3DAxis is not None
assert qtpy.QtDatavisualization.Q3DScene is not None
assert qtpy.QtDatavisualization.QCategory3DAxis is not None
assert qtpy.QtDatavisualization.QAbstract3DSeries is not None
assert qtpy.QtDatavisualization.Q3DObject is not None
assert qtpy.QtDatavisualization.QCustom3DLabel is not None
assert qtpy.QtDatavisualization.Q3DSurface is not None
assert qtpy.QtDatavisualization.QLogValue3DAxisFormatter is not None
5 changes: 1 addition & 4 deletions qtpy/tests/test_qtnetworkauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
from qtpy import PYQT5, PYQT6, PYSIDE2


@pytest.mark.skipif(
PYQT5 or PYQT6 or PYSIDE2,
reason="Not available by default in PyQt. Not available for PySide2",
)
@pytest.mark.skipif(PYSIDE2, reason="Not available for PySide2")
def test_qtnetworkauth():
"""Test the qtpy.QtNetworkAuth namespace"""
QtNetworkAuth = pytest.importorskip("qtpy.QtNetworkAuth")
Expand Down

0 comments on commit d7ff724

Please sign in to comment.