diff --git a/README.md b/README.md index 8663bb95..f25fd111 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,10 @@ default unless you set the `QT_API` environment variable. * `pyqt6` (to use PyQt6). * `pyside6` (to use PySide6). +Alternatively the `QT_VERSION` environment variable can be used with the following values: + +* `qt5` (to use PyQt5 or PySide2). +* `qt6` (to use PyQt6 or PySide6). ### Module aliases and constants diff --git a/qtpy/__init__.py b/qtpy/__init__.py index 5467fda5..84c4ce4e 100644 --- a/qtpy/__init__.py +++ b/qtpy/__init__.py @@ -140,9 +140,13 @@ def __init__(self, *, missing_package=None, **superclass_kwargs): # Qt API environment variable name QT_API = "QT_API" +# Qt VERSION environment variable name +QT_VERSION_ENV = "QT_VERSION" + # Names of the expected PyQt5 api PYQT5_API = ["pyqt5"] +# Names of the expected PyQt6 api PYQT6_API = ["pyqt6"] # Names of the expected PySide2 api @@ -151,6 +155,12 @@ def __init__(self, *, missing_package=None, **superclass_kwargs): # Names of the expected PySide6 api PYSIDE6_API = ["pyside6"] +# Names of the expected Qt5 version name +QT5_VERSIONS = ["qt5"] + +# Names of the expected Qt6 version name +QT6_VERSIONS = ["qt6"] + # Minimum supported versions of Qt and the bindings QT5_VERSION_MIN = PYQT5_VERSION_MIN = "5.9.0" PYSIDE2_VERSION_MIN = "5.12.0" @@ -160,8 +170,9 @@ def __init__(self, *, missing_package=None, **superclass_kwargs): PYQT_VERSION_MIN = PYQT5_VERSION_MIN PYSIDE_VERSION_MIN = PYSIDE2_VERSION_MIN -# Detecting if a binding was specified by the user +# Detecting if a binding or version was specified by the user binding_specified = QT_API in os.environ +version_specified = QT_VERSION_ENV in os.environ API_NAMES = { "pyqt5": "PyQt5", @@ -173,10 +184,42 @@ def __init__(self, *, missing_package=None, **superclass_kwargs): initial_api = API if API not in API_NAMES: raise PythonQtValueError( - f"Specified QT_API={QT_API.lower()!r} is not in valid options: " + f"Specified QT_API={API.lower()!r} is not in valid options: " f"{API_NAMES}", ) +VERSION_NAMES = { + "qt5": ("PyQt5", "PySide2"), + "qt6": ("PyQt6", "PySide6"), +} +requested_version = os.environ.get(QT_VERSION_ENV, "qt5").lower() +if requested_version not in VERSION_NAMES: + raise PythonQtValueError( + f"Specified QT_VERSION={requested_version.lower()!r} is not in valid options: " + f"{VERSION_NAMES}", + ) + +# If only QT_VERSION is given select API based on VERSION +if version_specified and not binding_specified: + if requested_version in QT5_VERSIONS: + API = PYQT5_API[0] + if requested_version in QT6_VERSIONS: + API = PYQT6_API[0] + + +# Warn if the QT_API and the QT_VERSION environment variables are incompatible +if ( + API_NAMES[initial_api] not in VERSION_NAMES[requested_version] + and version_specified + and binding_specified +): + warnings.warn( + f"Specified QT_API={initial_api!r} and specified QT_VERSION={requested_version!r} are incompatible, QT_API will take precedence.", + PythonQtWarning, + stacklevel=2, + ) + + is_old_pyqt = is_pyqt46 = False QT5 = PYQT5 = True QT4 = QT6 = PYQT4 = PYQT6 = PYSIDE = PYSIDE2 = PYSIDE6 = False @@ -299,6 +342,19 @@ def __init__(self, *, missing_package=None, **superclass_kwargs): stacklevel=2, ) +# Warn if the requested QT_VERSION could not be satisfied +if ( + API_NAMES[API] not in VERSION_NAMES[requested_version] + and version_specified + and not binding_specified +): + warnings.warn( + f"No binding was found for the selected version {requested_version!r}; " + f"falling back to {API!r}", + PythonQtWarning, + stacklevel=2, + ) + # Set display name of the Qt API API_NAME = API_NAMES[API] diff --git a/qtpy/tests/test_main.py b/qtpy/tests/test_main.py index 771c4894..347521b2 100644 --- a/qtpy/tests/test_main.py +++ b/qtpy/tests/test_main.py @@ -66,6 +66,34 @@ def assert_pyqt6(): assert os.environ["QT_API"] == "pyqt6" +def assert_qt5(): + try: + import PyQt5 + except ImportError: + try: + import PySide2 + except ImportError: + pytest.fail("No Qt5 API available.") + else: + assert_pyside2() + else: + assert_pyqt5() + + +def assert_qt6(): + try: + import PyQt6 + except ImportError: + try: + import PySide6 + except ImportError: + pytest.fail("No Qt6 API available.") + else: + assert_pyside6() + else: + assert_pyqt6() + + def test_qt_api(): """ If QT_API is specified, we check that the correct Qt wrapper was used @@ -140,3 +168,16 @@ def test_qt_api_environ(api): raise AssertionError('QtPy imported despite bad QT_API') """ subprocess.check_call([sys.executable, "-Oc", cmd], env=env) + + +def test_qt_version(): + """ + If QT_VERSION is specified, check that one of the correct Qt wrappers was used + """ + + QT_VERSION = os.environ.get("QT_VERSION", "").lower() + + if QT_VERSION == "qt5": + assert_qt5() + elif QT_VERSION == "qt6": + assert_qt6()