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:
uses: pyapp-kit/workflows/.github/workflows/test-pyrepo.yml@v2
with:
python-version: "3.8"
python-version: "3.9"
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
coverage-upload: artifact

View File

@@ -26,7 +26,7 @@ pytest
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
- PySide2 (5.11 and above) & PySide6
- macOS, Windows, & Linux

View File

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

View File

@@ -10,7 +10,7 @@ QtWidgets module.
Components are tested on:
- macOS, Windows, & Linux
- Python 3.8 and above
- Python 3.9 and above
- PyQt5 (5.11 and above) & PyQt6
- 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.QtGui import QPainter, QPen
@@ -65,8 +65,8 @@ class DrawSignalsWidget(QWidget):
self._scrollTimer.timeout.connect(self._scroll)
self._scrollTimer.start()
self._signalActivations: Deque[int] = Deque()
self._throttledSignalActivations: Deque[int] = Deque()
self._signalActivations: deque[int] = deque()
self._throttledSignalActivations: deque[int] = deque()
def sizeHint(self):
return QSize(400, 200)
@@ -84,7 +84,7 @@ class DrawSignalsWidget(QWidget):
self.update()
def scrollAndCut(self, v: Deque[int], cutoff: int):
def scrollAndCut(self, v: deque[int], cutoff: int):
L = len(v)
for p in range(L):
v[p] += 1
@@ -121,7 +121,7 @@ class DrawSignalsWidget(QWidget):
p.drawLine(0, h2, w, h2)
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()
pen = QPen()
pen.setWidthF(2.0)

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
import warnings
from contextlib import suppress
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.QtGui import QColor, QPainter
@@ -19,6 +19,9 @@ from qtpy.QtWidgets import (
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()}
COLOR_ROLE = Qt.ItemDataRole.BackgroundRole

View File

@@ -3,7 +3,7 @@ from enum import Enum, EnumMeta, Flag
from functools import reduce
from itertools import combinations
from operator import or_
from typing import Optional, Tuple, TypeVar
from typing import Optional, TypeVar
from qtpy.QtCore import Signal
from qtpy.QtWidgets import QComboBox
@@ -47,7 +47,7 @@ def _get_name(enum_value: Enum):
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

View File

@@ -1,5 +1,3 @@
from typing import List
from qtpy.QtCore import Qt
from qtpy.QtGui import QFont, QFontMetrics, QTextLayout
@@ -36,7 +34,7 @@ class _GenericEliding:
self._ellipses_width = width
@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`.
Static method.
@@ -74,5 +72,5 @@ class _GenericEliding:
# join them
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())

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__"
@@ -69,7 +70,7 @@ class IconFont(metaclass=IconFontMeta):
__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."""
if isinstance(namespace, type):
if not isinstance(getattr(namespace, FONTFILE_ATTR), str):

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
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.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.QtCore import Property, QEvent, QPoint, QPointF, QRect, QRectF, Qt, Signal
@@ -42,11 +43,11 @@ class _GenericRangeSlider(_GenericSlider):
self.valueChanged = self._valuesChanged
self.sliderMoved = self._slidersMoved
# 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
# 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
self._pressedIndex = 0
@@ -113,7 +114,7 @@ class _GenericRangeSlider(_GenericSlider):
# ############### QtOverrides #######################
def value(self) -> Tuple[_T, ...]:
def value(self) -> tuple[_T, ...]:
"""Get current value of the widget as a tuple of integers."""
return tuple(self._value)
@@ -332,7 +333,7 @@ class _GenericRangeSlider(_GenericSlider):
# NOTE: this is very much tied to mousepress... not a generic "get control"
def _getControlAtPos(
self, pos: QPoint, opt: Optional[QStyleOptionSlider] = None
) -> Tuple[QStyle.SubControl, int]:
) -> tuple[QStyle.SubControl, int]:
"""Update self._pressedControl based on ev.pos()."""
opt = opt or self._styleOption

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
import contextlib
from enum import IntEnum, IntFlag, auto
from functools import partial
from typing import Any, Iterable, overload
from typing import TYPE_CHECKING, Any, overload
from qtpy import QtGui
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
if TYPE_CHECKING:
from collections.abc import Iterable
class LabelPosition(IntEnum):
NoLabel = 0

View File

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

View File

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

View File

@@ -1,5 +1,3 @@
from typing import List, Tuple
import pytest
from pytestqt.qtbot import QtBot
from qtpy.QtCore import Qt
@@ -30,15 +28,15 @@ def widget(qtbot: QtBot, data: dict) -> QSearchableTreeWidget:
return widget
def columns(item: QTreeWidgetItem) -> Tuple[str, str]:
def columns(item: QTreeWidgetItem) -> tuple[str, str]:
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)
def shown_items(tree: QTreeWidget) -> List[QTreeWidgetItem]:
def shown_items(tree: QTreeWidget) -> list[QTreeWidgetItem]:
items = all_items(tree)
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
import pytest

View File

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