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

PR: Sever the dependency on packaging.version (dependencies) #508

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions qtpy/QtCore.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@
import contextlib
from typing import TYPE_CHECKING

from packaging.version import parse

from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6
from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6, _parse_version
from . import QT_VERSION as _qt_version
from ._utils import possibly_static_exec, possibly_static_exec_

Expand Down Expand Up @@ -159,7 +157,7 @@
)

# Passing as default value 0 in the same way PySide6 6.3.2 does for the `Qt.ItemFlags` definition.
if parse(_qt_version) > parse("6.3"):
if _parse_version(_qt_version) > _parse_version("6.3"):
Qt.ItemFlags = lambda value=0: Qt.ItemFlag(value)

# For issue #153 and updated for issue #305
Expand Down
6 changes: 2 additions & 4 deletions qtpy/QtGui.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@

from functools import partialmethod

from packaging.version import parse

from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6
from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6, _parse_version
from . import QT_VERSION as _qt_version
from ._utils import (
getattr_missing_optional_dep,
Expand Down Expand Up @@ -265,7 +263,7 @@ def movePositionPatched(
QDropEvent.posF = lambda self: self.position()


if PYQT5 or PYSIDE2 or parse(_qt_version) < parse("6.4"):
if PYQT5 or PYSIDE2 or _parse_version(_qt_version) < _parse_version("6.4"):
# Make `QAction.setShortcut` and `QAction.setShortcuts` compatible with Qt>=6.4
_action_set_shortcut = partialmethod(
set_shortcut,
Expand Down
12 changes: 5 additions & 7 deletions qtpy/QtWidgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
"""Provides widget classes and functions."""
from functools import partialmethod

from packaging.version import parse

from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6
from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6, _parse_version
from . import QT_VERSION as _qt_version
from ._utils import (
add_action,
Expand Down Expand Up @@ -44,7 +42,7 @@ def __getattr__(name):
QUndoCommand,
)

if parse(_qt_version) < parse("6.4"):
if _parse_version(_qt_version) < _parse_version("6.4"):
# Make `QAction.setShortcut` and `QAction.setShortcuts` compatible with Qt>=6.4
# See spyder-ide/qtpy#461
from qtpy.QtGui import QAction
Expand Down Expand Up @@ -120,7 +118,7 @@ def __getattr__(name):
elif PYSIDE6:
from PySide6.QtGui import QActionGroup, QShortcut, QUndoCommand

if parse(_qt_version) < parse("6.4"):
if _parse_version(_qt_version) < _parse_version("6.4"):
# Make `QAction.setShortcut` and `QAction.setShortcuts` compatible with Qt>=6.4
# See spyder-ide/qtpy#461
from qtpy.QtGui import QAction
Expand Down Expand Up @@ -175,7 +173,7 @@ def __getattr__(name):
)

# Passing as default value 0 in the same way PySide6 < 6.3.2 does for the `QFileDialog.Options` definition.
if parse(_qt_version) > parse("6.3"):
if _parse_version(_qt_version) > _parse_version("6.3"):
QFileDialog.Options = lambda value=0: QFileDialog.Option(value)


Expand Down Expand Up @@ -224,7 +222,7 @@ def __getattr__(name):
"directory",
)

if PYQT5 or PYSIDE2 or parse(_qt_version) < parse("6.4"):
if PYQT5 or PYSIDE2 or _parse_version(_qt_version) < _parse_version("6.4"):
# Make `addAction` compatible with Qt6 >= 6.4
_menu_add_action = partialmethod(
add_action,
Expand Down
68 changes: 53 additions & 15 deletions qtpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@
import sys
import warnings

from packaging.version import parse

# Version of QtPy
__version__ = "2.5.0.dev0"

Expand Down Expand Up @@ -185,6 +183,30 @@ def __init__(self, *, missing_package=None, **superclass_kwargs):
PYSIDE_VERSION = None
QT_VERSION = None


def _parse_int(value):
"""Convert a value into an integer"""
try:
return int(value)
except ValueError:
return 0


def _parse_version(version):
"""Parse a version into a comparable object"""
try:
from packaging.version import parse as _packaging_version_parse
except ImportError:
return _parse_version_internal(version)
else:
return _packaging_version_parse(version)


def _parse_version_internal(version):
"""Parse a version string into a tuple of ints"""
return tuple(_parse_int(x) for x in version.split("."))


# Unless `FORCE_QT_API` is set, use previously imported Qt Python bindings
if not os.environ.get("FORCE_QT_API"):
if "PyQt5" in sys.modules:
Expand All @@ -208,16 +230,20 @@ def __init__(self, *, missing_package=None, **superclass_kwargs):
QT5 = PYQT5 = True

if sys.platform == "darwin":
macos_version = parse(platform.mac_ver()[0])
qt_ver = parse(QT_VERSION)
if macos_version < parse("10.10") and qt_ver >= parse("5.9"):
macos_version = _parse_version(platform.mac_ver()[0])
qt_ver = _parse_version(QT_VERSION)
if macos_version < _parse_version(
"10.10",
) and qt_ver >= _parse_version("5.9"):
raise PythonQtError(
"Qt 5.9 or higher only works in "
"macOS 10.10 or higher. Your "
"program will fail in this "
"system.",
)
elif macos_version < parse("10.11") and qt_ver >= parse("5.11"):
elif macos_version < _parse_version(
"10.11",
) and qt_ver >= _parse_version("5.11"):
raise PythonQtError(
"Qt 5.11 or higher only works in "
"macOS 10.11 or higher. Your "
Expand All @@ -241,9 +267,11 @@ def __init__(self, *, missing_package=None, **superclass_kwargs):
QT5 = PYSIDE2 = True

if sys.platform == "darwin":
macos_version = parse(platform.mac_ver()[0])
qt_ver = parse(QT_VERSION)
if macos_version < parse("10.11") and qt_ver >= parse("5.11"):
macos_version = _parse_version(platform.mac_ver()[0])
qt_ver = _parse_version(QT_VERSION)
if macos_version < _parse_version(
"10.11",
) and qt_ver >= _parse_version("5.11"):
raise PythonQtError(
"Qt 5.11 or higher only works in "
"macOS 10.11 or higher. Your "
Expand Down Expand Up @@ -327,18 +355,28 @@ def _warn_old_minor_version(name, old_version, min_version):

# Warn if using an End of Life or unsupported Qt API/binding minor version
if QT_VERSION:
if QT5 and (parse(QT_VERSION) < parse(QT5_VERSION_MIN)):
if QT5 and (_parse_version(QT_VERSION) < _parse_version(QT5_VERSION_MIN)):
_warn_old_minor_version("Qt5", QT_VERSION, QT5_VERSION_MIN)
elif QT6 and (parse(QT_VERSION) < parse(QT6_VERSION_MIN)):
elif QT6 and (
_parse_version(QT_VERSION) < _parse_version(QT6_VERSION_MIN)
):
_warn_old_minor_version("Qt6", QT_VERSION, QT6_VERSION_MIN)

if PYQT_VERSION:
if PYQT5 and (parse(PYQT_VERSION) < parse(PYQT5_VERSION_MIN)):
if PYQT5 and (
_parse_version(PYQT_VERSION) < _parse_version(PYQT5_VERSION_MIN)
):
_warn_old_minor_version("PyQt5", PYQT_VERSION, PYQT5_VERSION_MIN)
elif PYQT6 and (parse(PYQT_VERSION) < parse(PYQT6_VERSION_MIN)):
elif PYQT6 and (
_parse_version(PYQT_VERSION) < _parse_version(PYQT6_VERSION_MIN)
):
_warn_old_minor_version("PyQt6", PYQT_VERSION, PYQT6_VERSION_MIN)
elif PYSIDE_VERSION:
if PYSIDE2 and (parse(PYSIDE_VERSION) < parse(PYSIDE2_VERSION_MIN)):
if PYSIDE2 and (
_parse_version(PYSIDE_VERSION) < _parse_version(PYSIDE2_VERSION_MIN)
):
_warn_old_minor_version("PySide2", PYSIDE_VERSION, PYSIDE2_VERSION_MIN)
elif PYSIDE6 and (parse(PYSIDE_VERSION) < parse(PYSIDE6_VERSION_MIN)):
elif PYSIDE6 and (
_parse_version(PYSIDE_VERSION) < _parse_version(PYSIDE6_VERSION_MIN)
):
_warn_old_minor_version("PySide6", PYSIDE_VERSION, PYSIDE6_VERSION_MIN)
18 changes: 9 additions & 9 deletions qtpy/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@
from functools import wraps
from typing import TYPE_CHECKING

import qtpy
from . import QtModuleNotInstalledError

if TYPE_CHECKING:
from qtpy.QtWidgets import QAction
from .QtWidgets import QAction


def _wrap_missing_optional_dep_error(
attr_error,
*,
import_error,
wrapper=qtpy.QtModuleNotInstalledError,
wrapper=QtModuleNotInstalledError,
**wrapper_kwargs,
):
"""Create a __cause__-chained wrapper error for a missing optional dep."""
Expand Down Expand Up @@ -72,8 +72,8 @@ def possibly_static_exec_(cls, *args, **kwargs):

def set_shortcut(self, shortcut, old_set_shortcut):
"""Ensure that the type of `shortcut` is compatible to `QAction.setShortcut`."""
from qtpy.QtCore import Qt
from qtpy.QtGui import QKeySequence
from .QtCore import Qt
from .QtGui import QKeySequence

if isinstance(shortcut, (QKeySequence.StandardKey, Qt.Key, int)):
shortcut = QKeySequence(shortcut)
Expand All @@ -82,8 +82,8 @@ def set_shortcut(self, shortcut, old_set_shortcut):

def set_shortcuts(self, shortcuts, old_set_shortcuts):
"""Ensure that the type of `shortcuts` is compatible to `QAction.setShortcuts`."""
from qtpy.QtCore import Qt
from qtpy.QtGui import QKeySequence
from .QtCore import Qt
from .QtGui import QKeySequence

if isinstance(
shortcuts,
Expand All @@ -104,8 +104,8 @@ def set_shortcuts(self, shortcuts, old_set_shortcuts):

def add_action(self, *args, old_add_action):
"""Re-order arguments of `addAction` to backport compatibility with Qt>=6.3."""
from qtpy.QtCore import QObject, Qt
from qtpy.QtGui import QIcon, QKeySequence
from .QtCore import QObject, Qt
from .QtGui import QIcon, QKeySequence

action: QAction
icon: QIcon
Expand Down
27 changes: 26 additions & 1 deletion qtpy/tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@

import pytest

from qtpy import API_NAMES, QtCore, QtGui, QtWidgets
from qtpy import (
API_NAMES,
QtCore,
QtGui,
QtWidgets,
_parse_version,
_parse_version_internal,
)
from qtpy.tests.utils import pytest_importorskip

with contextlib.suppress(Exception):
Expand Down Expand Up @@ -141,3 +148,21 @@ def test_qt_api_environ(api):
raise AssertionError('QtPy imported despite bad QT_API')
"""
subprocess.check_call([sys.executable, "-Oc", cmd], env=env)


@pytest.mark.parametrize(
"first,second",
[("1.2.3", "1.2.3.1"), ("1.2.3", "1.10.0")],
)
def test_parse_version(first, second):
"""Verify the behavior of _parse_version()"""
assert _parse_version(first) < _parse_version(second)


@pytest.mark.parametrize(
"value,expect",
[("1.2.3", (1, 2, 3)), ("1.x.3", (1, 0, 3))],
)
def test_parse_version_internal(value, expect):
"""Verify the behavior of _parse_version_internal()"""
assert _parse_version_internal(value) == expect
5 changes: 2 additions & 3 deletions qtpy/tests/test_qtconcurrent.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import pytest
from packaging.version import parse

from qtpy import PYSIDE2, PYSIDE_VERSION
from qtpy import PYSIDE2, PYSIDE_VERSION, _parse_version
from qtpy.tests.utils import pytest_importorskip


Expand All @@ -11,7 +10,7 @@ def test_qtconcurrent():

assert QtConcurrent.QtConcurrent is not None

if PYSIDE2 and parse(PYSIDE_VERSION) >= parse("5.15.2"):
if PYSIDE2 and _parse_version(PYSIDE_VERSION) >= _parse_version("5.15.2"):
assert QtConcurrent.QFutureQString is not None
assert QtConcurrent.QFutureVoid is not None
assert QtConcurrent.QFutureWatcherQString is not None
Expand Down
4 changes: 2 additions & 2 deletions qtpy/tests/test_qtcore.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from datetime import date, datetime, time

import pytest
from packaging.version import parse

from qtpy import (
PYQT5,
Expand All @@ -14,6 +13,7 @@
PYSIDE2,
PYSIDE_VERSION,
QtCore,
_parse_version,
)

_now = datetime.now()
Expand Down Expand Up @@ -71,7 +71,7 @@ def test_qthread_exec():


@pytest.mark.skipif(
PYSIDE2 and parse(PYSIDE_VERSION) < parse("5.15"),
PYSIDE2 and _parse_version(PYSIDE_VERSION) < _parse_version("5.15"),
reason="QEnum macro doesn't seem to be present on PySide2 <5.15",
)
def test_qenum():
Expand Down
4 changes: 2 additions & 2 deletions qtpy/tests/test_qtgui.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import sys

import pytest
from packaging.version import parse

from qtpy import (
PYQT5,
Expand All @@ -14,6 +13,7 @@
QtCore,
QtGui,
QtWidgets,
_parse_version,
)
from qtpy.tests.utils import not_using_conda

Expand Down Expand Up @@ -198,7 +198,7 @@ def test_QAction_functions(qtbot):


@pytest.mark.skipif(
parse(QT_VERSION) < parse("6.5.0"),
_parse_version(QT_VERSION) < _parse_version("6.5.0"),
reason="Qt6 >= 6.5 specific test",
)
@pytest.mark.skipif(
Expand Down
5 changes: 2 additions & 3 deletions qtpy/tests/test_qttest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import pytest
from packaging import version

from qtpy import PYQT5, PYQT6, PYQT_VERSION, PYSIDE6, QtTest
from qtpy import PYQT5, PYQT6, PYQT_VERSION, PYSIDE6, QtTest, _parse_version


def test_qttest():
Expand All @@ -12,7 +11,7 @@ def test_qttest():
assert QtTest.QSignalSpy is not None

if (
(PYQT5 and version.parse(PYQT_VERSION) >= version.parse("5.11"))
(PYQT5 and _parse_version(PYQT_VERSION) >= _parse_version("5.11"))
or PYQT6
or PYSIDE6
):
Expand Down
5 changes: 2 additions & 3 deletions qtpy/tests/test_qttexttospeech.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import pytest
from packaging import version

from qtpy import PYQT5, PYQT_VERSION, PYSIDE2
from qtpy import PYQT5, PYQT_VERSION, PYSIDE2, _parse_version


@pytest.mark.skipif(
not (
(PYQT5 and version.parse(PYQT_VERSION) >= version.parse("5.15.1"))
(PYQT5 and _parse_version(PYQT_VERSION) >= _parse_version("5.15.1"))
or PYSIDE2
),
reason="Only available in Qt5 bindings (PyQt5 >= 5.15.1 or PySide2)",
Expand Down
Loading
Loading