build: drop py38 (#263)

* build: drop py38

* bump min typing ext

* ignore cleanup warning from pyside6

* change minreq

* bump min

* fix for pint again
This commit is contained in:
Talley Lambert
2024-12-13 09:30:27 -05:00
committed by GitHub
parent 2f3113f0f6
commit 8a40170c89
22 changed files with 99 additions and 63 deletions

View File

@@ -68,9 +68,9 @@ jobs:
test-qt-minreqs: test-qt-minreqs:
uses: pyapp-kit/workflows/.github/workflows/test-pyrepo.yml@v2 uses: pyapp-kit/workflows/.github/workflows/test-pyrepo.yml@v2
with: with:
python-version: "3.8" python-version: "3.9"
qt: pyqt5 qt: pyqt5
pip-post-installs: "qtpy==1.1.0 typing-extensions==3.7.4.3" pip-post-installs: "qtpy==1.1.0 typing-extensions==4.5.0" # 4.5.0 is just for pint
pip-install-flags: -e pip-install-flags: -e
coverage-upload: artifact coverage-upload: artifact

View File

@@ -26,7 +26,7 @@ pytest
All widgets must be well-tested, and should work on: All widgets must be well-tested, and should work on:
- Python 3.8 and above - Python 3.9 and above
- PyQt5 (5.11 and above) & PyQt6 - PyQt5 (5.11 and above) & PyQt6
- PySide2 (5.11 and above) & PySide6 - PySide2 (5.11 and above) & PySide6
- macOS, Windows, & Linux - macOS, Windows, & Linux

View File

@@ -15,7 +15,7 @@ that are not provided in the native QtWidgets module.
Components are tested on: Components are tested on:
- macOS, Windows, & Linux - macOS, Windows, & Linux
- Python 3.8 and above - Python 3.9 and above
- PyQt5 (5.11 and above) & PyQt6 - PyQt5 (5.11 and above) & PyQt6
- PySide2 (5.11 and above) & PySide6 - PySide2 (5.11 and above) & PySide6

View File

@@ -10,7 +10,7 @@ QtWidgets module.
Components are tested on: Components are tested on:
- macOS, Windows, & Linux - macOS, Windows, & Linux
- Python 3.8 and above - Python 3.9 and above
- PyQt5 (5.11 and above) & PyQt6 - PyQt5 (5.11 and above) & PyQt6
- PySide2 (5.11 and above) & PySide6 - PySide2 (5.11 and above) & PySide6

View File

@@ -27,7 +27,7 @@ SOFTWARE.
""" """
from typing import Deque from collections import deque
from qtpy.QtCore import QRect, QSize, Qt, QTimer, Signal from qtpy.QtCore import QRect, QSize, Qt, QTimer, Signal
from qtpy.QtGui import QPainter, QPen from qtpy.QtGui import QPainter, QPen
@@ -65,8 +65,8 @@ class DrawSignalsWidget(QWidget):
self._scrollTimer.timeout.connect(self._scroll) self._scrollTimer.timeout.connect(self._scroll)
self._scrollTimer.start() self._scrollTimer.start()
self._signalActivations: Deque[int] = Deque() self._signalActivations: deque[int] = deque()
self._throttledSignalActivations: Deque[int] = Deque() self._throttledSignalActivations: deque[int] = deque()
def sizeHint(self): def sizeHint(self):
return QSize(400, 200) return QSize(400, 200)
@@ -84,7 +84,7 @@ class DrawSignalsWidget(QWidget):
self.update() self.update()
def scrollAndCut(self, v: Deque[int], cutoff: int): def scrollAndCut(self, v: deque[int], cutoff: int):
L = len(v) L = len(v)
for p in range(L): for p in range(L):
v[p] += 1 v[p] += 1
@@ -121,7 +121,7 @@ class DrawSignalsWidget(QWidget):
p.drawLine(0, h2, w, h2) p.drawLine(0, h2, w, h2)
p.restore() p.restore()
def _drawSignals(self, p: QPainter, v: Deque[int], color, yStart, yEnd): def _drawSignals(self, p: QPainter, v: deque[int], color, yStart, yEnd):
p.save() p.save()
pen = QPen() pen = QPen()
pen.setWidthF(2.0) pen.setWidthF(2.0)

View File

@@ -8,7 +8,7 @@ build-backend = "hatchling.build"
name = "superqt" name = "superqt"
description = "Missing widgets and components for PyQt/PySide" description = "Missing widgets and components for PyQt/PySide"
readme = "README.md" readme = "README.md"
requires-python = ">=3.8" requires-python = ">=3.9"
license = { text = "BSD 3-Clause License" } license = { text = "BSD 3-Clause License" }
authors = [{ email = "talley.lambert@gmail.com", name = "Talley Lambert" }] authors = [{ email = "talley.lambert@gmail.com", name = "Talley Lambert" }]
keywords = [ keywords = [
@@ -28,7 +28,6 @@ classifiers = [
"Operating System :: OS Independent", "Operating System :: OS Independent",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
@@ -41,13 +40,21 @@ dynamic = ["version"]
dependencies = [ dependencies = [
"pygments>=2.4.0", "pygments>=2.4.0",
"qtpy>=1.1.0", "qtpy>=1.1.0",
"typing-extensions >=3.7.4.3,!=3.10.0.0", "typing-extensions >=3.7.4.3,!=3.10.0.0", # however, pint requires >4.5.0
] ]
# extras # extras
# https://peps.python.org/pep-0621/#dependencies-optional-dependencies # https://peps.python.org/pep-0621/#dependencies-optional-dependencies
[project.optional-dependencies] [project.optional-dependencies]
test = ["pint", "pytest", "pytest-cov", "pytest-qt", "numpy", "cmap", "pyconify"] test = [
"pint",
"pytest",
"pytest-cov",
"pytest-qt",
"numpy",
"cmap",
"pyconify",
]
dev = [ dev = [
"ipython", "ipython",
"ruff", "ruff",
@@ -58,7 +65,13 @@ dev = [
"rich", "rich",
"types-Pygments", "types-Pygments",
] ]
docs = ["mkdocs-macros-plugin", "mkdocs-material", "mkdocstrings[python]", "pint", "cmap"] docs = [
"mkdocs-macros-plugin",
"mkdocs-material",
"mkdocstrings[python]",
"pint",
"cmap",
]
quantity = ["pint"] quantity = ["pint"]
cmap = ["cmap >=0.1.1"] cmap = ["cmap >=0.1.1"]
pyside2 = ["pyside2"] pyside2 = ["pyside2"]
@@ -100,21 +113,30 @@ python = ["3.11"]
[[tool.hatch.envs.test.matrix]] [[tool.hatch.envs.test.matrix]]
qt = ["pyside2", "pyqt5", "pyqt5.12"] qt = ["pyside2", "pyqt5", "pyqt5.12"]
python = ["3.8"] python = ["3.9"]
[tool.hatch.envs.test.overrides] [tool.hatch.envs.test.overrides]
matrix.qt.extra-dependencies = [ matrix.qt.extra-dependencies = [
{value = "pyside2", if = ["pyside2"]}, { value = "pyside2", if = [
{value = "pyside6", if = ["pyside6"]}, "pyside2",
{value = "pyqt5", if = ["pyqt5"]}, ] },
{value = "pyqt6", if = ["pyqt6"]}, { value = "pyside6", if = [
{value = "pyqt5==5.12", if = ["pyqt5.12"]}, "pyside6",
] },
{ value = "pyqt5", if = [
"pyqt5",
] },
{ value = "pyqt6", if = [
"pyqt6",
] },
{ value = "pyqt5==5.12", if = [
"pyqt5.12",
] },
] ]
# https://github.com/charliermarsh/ruff
[tool.ruff] [tool.ruff]
line-length = 88 line-length = 88
target-version = "py38" target-version = "py39"
src = ["src", "tests"] src = ["src", "tests"]
# https://docs.astral.sh/ruff/rules # https://docs.astral.sh/ruff/rules
@@ -132,7 +154,7 @@ select = [
"B", # flake8-bugbear "B", # flake8-bugbear
"A001", # flake8-builtins "A001", # flake8-builtins
"RUF", # ruff-specific rules "RUF", # ruff-specific rules
"TCH", # flake8-type-checking "TC", # flake8-type-checking
"TID", # flake8-tidy-imports "TID", # flake8-tidy-imports
] ]
ignore = [ ignore = [
@@ -159,6 +181,7 @@ filterwarnings = [
"ignore:QPixmapCache.find:DeprecationWarning:", "ignore:QPixmapCache.find:DeprecationWarning:",
"ignore:SelectableGroups dict interface:DeprecationWarning", "ignore:SelectableGroups dict interface:DeprecationWarning",
"ignore:The distutils package is deprecated:DeprecationWarning", "ignore:The distutils package is deprecated:DeprecationWarning",
"ignore:.*Skipping callback call set_result",
] ]
# https://mypy.readthedocs.io/en/stable/config_file.html # https://mypy.readthedocs.io/en/stable/config_file.html

View File

@@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING, Container from typing import TYPE_CHECKING
from cmap import Colormap from cmap import Colormap
from qtpy.QtCore import Qt, Signal from qtpy.QtCore import Qt, Signal
@@ -11,6 +11,8 @@ from ._cmap_line_edit import QColormapLineEdit
from ._cmap_utils import try_cast_colormap from ._cmap_utils import try_cast_colormap
if TYPE_CHECKING: if TYPE_CHECKING:
from collections.abc import Container
from cmap._catalog import Category, Interpolation from cmap._catalog import Category, Interpolation
from qtpy.QtGui import QKeyEvent from qtpy.QtGui import QKeyEvent

View File

@@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING, Any, Sequence from typing import TYPE_CHECKING, Any
from cmap import Colormap from cmap import Colormap
from qtpy.QtCore import Qt, Signal from qtpy.QtCore import Qt, Signal
@@ -23,6 +23,8 @@ from ._cmap_line_edit import QColormapLineEdit
from ._cmap_utils import try_cast_colormap from ._cmap_utils import try_cast_colormap
if TYPE_CHECKING: if TYPE_CHECKING:
from collections.abc import Sequence
from cmap._colormap import ColorStopsLike from cmap._colormap import ColorStopsLike

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
import warnings import warnings
from contextlib import suppress from contextlib import suppress
from enum import IntEnum, auto from enum import IntEnum, auto
from typing import Any, Literal, Sequence, cast from typing import TYPE_CHECKING, Any, Literal, cast
from qtpy.QtCore import QModelIndex, QPersistentModelIndex, QRect, QSize, Qt, Signal from qtpy.QtCore import QModelIndex, QPersistentModelIndex, QRect, QSize, Qt, Signal
from qtpy.QtGui import QColor, QPainter from qtpy.QtGui import QColor, QPainter
@@ -19,6 +19,9 @@ from qtpy.QtWidgets import (
from superqt.utils import signals_blocked from superqt.utils import signals_blocked
if TYPE_CHECKING:
from collections.abc import Sequence
_NAME_MAP = {QColor(x).name(): x for x in QColor.colorNames()} _NAME_MAP = {QColor(x).name(): x for x in QColor.colorNames()}
COLOR_ROLE = Qt.ItemDataRole.BackgroundRole COLOR_ROLE = Qt.ItemDataRole.BackgroundRole

View File

@@ -3,7 +3,7 @@ from enum import Enum, EnumMeta, Flag
from functools import reduce from functools import reduce
from itertools import combinations from itertools import combinations
from operator import or_ from operator import or_
from typing import Optional, Tuple, TypeVar from typing import Optional, TypeVar
from qtpy.QtCore import Signal from qtpy.QtCore import Signal
from qtpy.QtWidgets import QComboBox from qtpy.QtWidgets import QComboBox
@@ -47,7 +47,7 @@ def _get_name(enum_value: Enum):
return name return name
def _get_name_with_value(enum_value: Enum) -> Tuple[str, Enum]: def _get_name_with_value(enum_value: Enum) -> tuple[str, Enum]:
return _get_name(enum_value), enum_value return _get_name(enum_value), enum_value

View File

@@ -1,5 +1,3 @@
from typing import List
from qtpy.QtCore import Qt from qtpy.QtCore import Qt
from qtpy.QtGui import QFont, QFontMetrics, QTextLayout from qtpy.QtGui import QFont, QFontMetrics, QTextLayout
@@ -36,7 +34,7 @@ class _GenericEliding:
self._ellipses_width = width self._ellipses_width = width
@staticmethod @staticmethod
def wrapText(text, width, font=None) -> List[str]: def wrapText(text, width, font=None) -> list[str]:
"""Returns `text`, split as it would be wrapped for `width`, given `font`. """Returns `text`, split as it would be wrapped for `width`, given `font`.
Static method. Static method.
@@ -74,5 +72,5 @@ class _GenericEliding:
# join them # join them
return "".join(text[:nlines] + [last_line]) return "".join(text[:nlines] + [last_line])
def _wrappedText(self) -> List[str]: def _wrappedText(self) -> list[str]:
return _GenericEliding.wrapText(self._text, self.width(), self.font()) return _GenericEliding.wrapText(self._text, self.width(), self.font())

View File

@@ -1,4 +1,5 @@
from typing import Mapping, Type, Union from collections.abc import Mapping
from typing import Union
FONTFILE_ATTR = "__font_file__" FONTFILE_ATTR = "__font_file__"
@@ -69,7 +70,7 @@ class IconFont(metaclass=IconFontMeta):
__font_file__ = "..." __font_file__ = "..."
def namespace2font(namespace: Union[Mapping, Type], name: str) -> Type[IconFont]: def namespace2font(namespace: Union[Mapping, type], name: str) -> type[IconFont]:
"""Convenience to convert a namespace (class, module, dict) into an IconFont.""" """Convenience to convert a namespace (class, module, dict) into an IconFont."""
if isinstance(namespace, type): if isinstance(namespace, type):
if not isinstance(getattr(namespace, FONTFILE_ATTR), str): if not isinstance(getattr(namespace, FONTFILE_ATTR), str):

View File

@@ -1,5 +1,5 @@
import contextlib import contextlib
from typing import ClassVar, Dict, List, Set, Tuple from typing import ClassVar
from ._iconfont import IconFontMeta, namespace2font from ._iconfont import IconFontMeta, namespace2font
@@ -11,9 +11,9 @@ except ImportError:
class FontIconManager: class FontIconManager:
ENTRY_POINT: ClassVar[str] = "superqt.fonticon" ENTRY_POINT: ClassVar[str] = "superqt.fonticon"
_PLUGINS: ClassVar[Dict[str, EntryPoint]] = {} _PLUGINS: ClassVar[dict[str, EntryPoint]] = {}
_LOADED: ClassVar[Dict[str, IconFontMeta]] = {} _LOADED: ClassVar[dict[str, IconFontMeta]] = {}
_BLOCKED: ClassVar[Set[EntryPoint]] = set() _BLOCKED: ClassVar[set[EntryPoint]] = set()
def _discover_fonts(self) -> None: def _discover_fonts(self) -> None:
self._PLUGINS.clear() self._PLUGINS.clear()
@@ -86,15 +86,15 @@ _manager = FontIconManager()
get_font_class = _manager._get_font_class get_font_class = _manager._get_font_class
def discover() -> Tuple[str]: def discover() -> tuple[str]:
_manager._discover_fonts() _manager._discover_fonts()
def available() -> Tuple[str]: def available() -> tuple[str]:
return tuple(_manager._PLUGINS) return tuple(_manager._PLUGINS)
def loaded(load_all=False) -> Dict[str, List[str]]: def loaded(load_all=False) -> dict[str, list[str]]:
if load_all: if load_all:
discover() discover()
for x in available(): for x in available():

View File

@@ -2,9 +2,10 @@ from __future__ import annotations
import warnings import warnings
from collections import abc, defaultdict from collections import abc, defaultdict
from collections.abc import Sequence
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, ClassVar, DefaultDict, Sequence, Tuple, Union, cast from typing import TYPE_CHECKING, ClassVar, Union, cast
from qtpy import QT_VERSION from qtpy import QT_VERSION
from qtpy.QtCore import QObject, QPoint, QRect, QSize, Qt from qtpy.QtCore import QObject, QPoint, QRect, QSize, Qt
@@ -47,8 +48,8 @@ ValidColor = Union[
int, int,
str, str,
Qt.GlobalColor, Qt.GlobalColor,
Tuple[int, int, int, int], tuple[int, int, int, int],
Tuple[int, int, int], tuple[int, int, int],
None, None,
] ]
@@ -159,7 +160,7 @@ class _QFontIconEngine(QIconEngine):
def __init__(self, options: _IconOptions): def __init__(self, options: _IconOptions):
super().__init__() super().__init__()
self._opts: defaultdict[QIcon.State, dict[QIcon.Mode, _IconOptions | None]] = ( self._opts: defaultdict[QIcon.State, dict[QIcon.Mode, _IconOptions | None]] = (
DefaultDict(dict) defaultdict(dict)
) )
self._opts[QIcon.State.Off][QIcon.Mode.Normal] = options self._opts[QIcon.State.Off][QIcon.Mode.Normal] = options
self.update_hash() self.update_hash()

View File

@@ -1,5 +1,6 @@
import logging import logging
from typing import Any, Iterable, Mapping from collections.abc import Iterable, Mapping
from typing import Any
from qtpy.QtCore import QRegularExpression from qtpy.QtCore import QRegularExpression
from qtpy.QtWidgets import QLineEdit, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget from qtpy.QtWidgets import QLineEdit, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget

View File

@@ -1,4 +1,5 @@
from typing import List, Optional, Sequence, Tuple, TypeVar, Union from collections.abc import Sequence
from typing import Optional, TypeVar, Union
from qtpy import QtGui from qtpy import QtGui
from qtpy.QtCore import Property, QEvent, QPoint, QPointF, QRect, QRectF, Qt, Signal from qtpy.QtCore import Property, QEvent, QPoint, QPointF, QRect, QRectF, Qt, Signal
@@ -42,11 +43,11 @@ class _GenericRangeSlider(_GenericSlider):
self.valueChanged = self._valuesChanged self.valueChanged = self._valuesChanged
self.sliderMoved = self._slidersMoved self.sliderMoved = self._slidersMoved
# list of values # list of values
self._value: List[_T] = [20, 80] self._value: list[_T] = [20, 80]
# list of current positions of each handle. same length as _value # list of current positions of each handle. same length as _value
# If tracking is enabled (the default) this will be identical to _value # If tracking is enabled (the default) this will be identical to _value
self._position: List[_T] = [20, 80] self._position: list[_T] = [20, 80]
# which handle is being pressed/hovered # which handle is being pressed/hovered
self._pressedIndex = 0 self._pressedIndex = 0
@@ -113,7 +114,7 @@ class _GenericRangeSlider(_GenericSlider):
# ############### QtOverrides ####################### # ############### QtOverrides #######################
def value(self) -> Tuple[_T, ...]: def value(self) -> tuple[_T, ...]:
"""Get current value of the widget as a tuple of integers.""" """Get current value of the widget as a tuple of integers."""
return tuple(self._value) return tuple(self._value)
@@ -332,7 +333,7 @@ class _GenericRangeSlider(_GenericSlider):
# NOTE: this is very much tied to mousepress... not a generic "get control" # NOTE: this is very much tied to mousepress... not a generic "get control"
def _getControlAtPos( def _getControlAtPos(
self, pos: QPoint, opt: Optional[QStyleOptionSlider] = None self, pos: QPoint, opt: Optional[QStyleOptionSlider] = None
) -> Tuple[QStyle.SubControl, int]: ) -> tuple[QStyle.SubControl, int]:
"""Update self._pressedControl based on ev.pos().""" """Update self._pressedControl based on ev.pos()."""
opt = opt or self._styleOption opt = opt or self._styleOption

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
import contextlib import contextlib
from enum import IntEnum, IntFlag, auto from enum import IntEnum, IntFlag, auto
from functools import partial from functools import partial
from typing import Any, Iterable, overload from typing import TYPE_CHECKING, Any, overload
from qtpy import QtGui from qtpy import QtGui
from qtpy.QtCore import Property, QPoint, QSize, Qt, Signal from qtpy.QtCore import Property, QPoint, QSize, Qt, Signal
@@ -25,6 +25,9 @@ from superqt.utils import signals_blocked
from ._sliders import QDoubleRangeSlider, QDoubleSlider, QRangeSlider from ._sliders import QDoubleRangeSlider, QDoubleSlider, QRangeSlider
if TYPE_CHECKING:
from collections.abc import Iterable
class LabelPosition(IntEnum): class LabelPosition(IntEnum):
NoLabel = 0 NoLabel = 0

View File

@@ -1,5 +1,6 @@
from collections.abc import Iterator
from contextlib import contextmanager from contextlib import contextmanager
from typing import TYPE_CHECKING, Iterator from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from qtpy.QtCore import QObject from qtpy.QtCore import QObject

View File

@@ -9,9 +9,7 @@ from typing import (
Any, Any,
Callable, Callable,
ClassVar, ClassVar,
Generator,
Generic, Generic,
Sequence,
TypeVar, TypeVar,
overload, overload,
) )
@@ -19,6 +17,8 @@ from typing import (
from qtpy.QtCore import QObject, QRunnable, QThread, QThreadPool, QTimer, Signal from qtpy.QtCore import QObject, QRunnable, QThread, QThreadPool, QTimer, Signal
if TYPE_CHECKING: if TYPE_CHECKING:
from collections.abc import Generator, Sequence
_T = TypeVar("_T") _T = TypeVar("_T")
class SigInst(Generic[_T]): class SigInst(Generic[_T]):

View File

@@ -1,5 +1,3 @@
from typing import List, Tuple
import pytest import pytest
from pytestqt.qtbot import QtBot from pytestqt.qtbot import QtBot
from qtpy.QtCore import Qt from qtpy.QtCore import Qt
@@ -30,15 +28,15 @@ def widget(qtbot: QtBot, data: dict) -> QSearchableTreeWidget:
return widget return widget
def columns(item: QTreeWidgetItem) -> Tuple[str, str]: def columns(item: QTreeWidgetItem) -> tuple[str, str]:
return item.text(0), item.text(1) return item.text(0), item.text(1)
def all_items(tree: QTreeWidget) -> List[QTreeWidgetItem]: def all_items(tree: QTreeWidget) -> list[QTreeWidgetItem]:
return tree.findItems("", Qt.MatchContains | Qt.MatchRecursive) return tree.findItems("", Qt.MatchContains | Qt.MatchRecursive)
def shown_items(tree: QTreeWidget) -> List[QTreeWidgetItem]: def shown_items(tree: QTreeWidget) -> list[QTreeWidgetItem]:
items = all_items(tree) items = all_items(tree)
return [item for item in items if not item.isHidden()] return [item for item in items if not item.isHidden()]

View File

@@ -1,4 +1,5 @@
from typing import Any, Iterable from collections.abc import Iterable
from typing import Any
from unittest.mock import Mock from unittest.mock import Mock
import pytest import pytest

View File

@@ -1,6 +1,7 @@
import math import math
from collections.abc import Iterable
from itertools import product from itertools import product
from typing import Any, Iterable from typing import Any
from unittest.mock import Mock from unittest.mock import Mock
import pytest import pytest