From 6db5219542c9e2e5a7f2cdcc3bdc30d696a9e803 Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Thu, 21 Nov 2024 20:43:01 -0500 Subject: [PATCH] build: drop python 3.8 (#341) * drop py38 * pyup * no pydantic 2.10 * fix build in benchmark * try fix pyside2 test * fix install * narrow numpy<2 to pyside2 * try different python version --- .github/workflows/test.yml | 12 +++--- asv.conf.json | 2 +- pyproject.toml | 15 ++++---- src/psygnal/_dataclass_utils.py | 6 ++- src/psygnal/_evented_decorator.py | 4 +- src/psygnal/_evented_model.py | 38 +++++++++---------- src/psygnal/_exceptions.py | 4 +- src/psygnal/_group.py | 12 +++--- src/psygnal/_group_descriptor.py | 6 +-- .../_pyinstaller_util/_pyinstaller_hook.py | 3 +- src/psygnal/_pyinstaller_util/hook-psygnal.py | 7 ++-- src/psygnal/_queue.py | 15 +++----- src/psygnal/_signal.py | 18 ++++----- src/psygnal/containers/_evented_dict.py | 12 ++---- src/psygnal/containers/_evented_list.py | 4 +- src/psygnal/containers/_evented_proxy.py | 6 +-- src/psygnal/containers/_evented_set.py | 5 +-- .../containers/_selectable_evented_list.py | 5 ++- src/psygnal/containers/_selection.py | 5 ++- src/psygnal/utils.py | 5 ++- tests/containers/test_evented_list.py | 6 +-- tests/test_evented_model.py | 15 ++++---- tests/test_group_descriptor.py | 4 +- tests/test_qt_compat.py | 4 +- 24 files changed, 100 insertions(+), 113 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 81d7984b..197c957c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,7 +32,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] os: [ubuntu-latest, macos-latest, windows-latest] compile: [true, false] include: @@ -65,14 +65,14 @@ jobs: matrix: python-version: ["3.10"] os: [macos-latest, windows-latest] - qt: [PyQt5, PyQt6, PySide2, PySide6] + qt: [PyQt5, PyQt6, PySide6] compile: [true, false] - exclude: - - os: macos-latest - qt: PySide2 include: - os: macos-13 - qt: PySide2 + qt: "PySide2 'numpy<2'" + - os: windows-latest + python-version: "3.9" + qt: "PySide2 'numpy<2'" upload_coverage: if: always() diff --git a/asv.conf.json b/asv.conf.json index bd5ca378..1b521a24 100644 --- a/asv.conf.json +++ b/asv.conf.json @@ -10,7 +10,7 @@ "show_commit_url": "https://github.com/pyapp-kit/psygnal/commit/", "pythons": ["3.11"], "build_command": [ - "python -m pip install build 'hatchling==1.21.1' hatch-vcs hatch-mypyc mypy pydantic types-attrs msgspec", + "python -m pip install build 'hatchling==1.21.1' hatch-vcs hatch-mypyc mypy pydantic!=2.10.0 types-attrs msgspec", "python -c \"import os; from pathlib import Path; import hatchling.builders.wheel as h; p = Path(h.__file__); targ = os.environ.get('MACOSX_DEPLOYMENT_TARGET', '10_16').replace('.', '_'); txt = p.read_text().replace('10_16', targ); p.write_text(txt)\"", "python -m build --wheel -o {build_cache_dir} {build_dir} --no-isolation" ], diff --git a/pyproject.toml b/pyproject.toml index 1ec779e7..1b311486 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ build-backend = "hatchling.build" name = "psygnal" description = "Fast python callback/event system modeled after Qt Signals" readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.9" license = { text = "BSD 3-Clause License" } authors = [{ name = "Talley Lambert", email = "talley.lambert@gmail.com" }] classifiers = [ @@ -16,7 +16,6 @@ classifiers = [ "License :: OSI Approved :: BSD License", "Natural Language :: English", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -95,7 +94,7 @@ dependencies = [ "hatch-mypyc>=0.13.0", "mypy>=0.991", "mypy_extensions >=0.4.2", - "pydantic", + "pydantic!=2.10.0", "types-attrs", "msgspec", ] @@ -112,7 +111,7 @@ exclude = [ [tool.cibuildwheel] # Skip 32-bit builds & PyPy wheels on all platforms skip = ["*-manylinux_i686", "*-musllinux_i686", "*-win32", "pp*"] -build = ["cp38-*", "cp39-*", "cp310-*", "cp311-*", "cp312-*"] +build = ["cp39-*", "cp310-*", "cp311-*", "cp312-*", "cp313-*"] test-extras = ["test"] test-command = "pytest {project}/tests -v" test-skip = ["*-musllinux*", "cp312-win*", "*-macosx_arm64"] @@ -128,7 +127,7 @@ HATCH_BUILD_HOOKS_ENABLE = "1" # https://docs.astral.sh/ruff/ [tool.ruff] line-length = 88 -target-version = "py37" +target-version = "py39" src = ["src", "tests"] [tool.ruff.lint] @@ -150,7 +149,7 @@ select = [ "RUF", # ruff-specific rules ] ignore = [ - "D401", # First line should be in imperative mood + "D401", # First line should be in imperative mood ] [tool.ruff.lint.per-file-ignores] @@ -168,9 +167,9 @@ testpaths = ["tests"] filterwarnings = [ "error", "ignore:The distutils package is deprecated:DeprecationWarning:", - "ignore:.*BackendFinder.find_spec()", # pyinstaller import + "ignore:.*BackendFinder.find_spec()", # pyinstaller import "ignore:.*not using a cooperative constructor:pytest.PytestDeprecationWarning:", - "ignore:Failed to disconnect::pytestqt" + "ignore:Failed to disconnect::pytestqt", ] # https://mypy.readthedocs.io/en/stable/config_file.html diff --git a/src/psygnal/_dataclass_utils.py b/src/psygnal/_dataclass_utils.py index bf323408..69b039dd 100644 --- a/src/psygnal/_dataclass_utils.py +++ b/src/psygnal/_dataclass_utils.py @@ -4,9 +4,11 @@ import dataclasses import sys import types -from typing import TYPE_CHECKING, Any, Iterator, List, Protocol, cast, overload +from typing import TYPE_CHECKING, Any, Protocol, cast, overload if TYPE_CHECKING: + from collections.abc import Iterator + import attrs import msgspec from pydantic import BaseModel @@ -22,7 +24,7 @@ class _DataclassParams(Protocol): frozen: bool -GenericAlias = getattr(types, "GenericAlias", type(List[int])) # safe for < py 3.9 +GenericAlias = getattr(types, "GenericAlias", type(list[int])) # safe for < py 3.9 class AttrsType: diff --git a/src/psygnal/_evented_decorator.py b/src/psygnal/_evented_decorator.py index 4b084b9c..4ea4b2ee 100644 --- a/src/psygnal/_evented_decorator.py +++ b/src/psygnal/_evented_decorator.py @@ -1,10 +1,12 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable, Literal, Mapping, TypeVar, overload +from typing import TYPE_CHECKING, Callable, Literal, TypeVar, overload from psygnal._group_descriptor import SignalGroupDescriptor if TYPE_CHECKING: + from collections.abc import Mapping + from psygnal._group_descriptor import EqOperator, FieldAliasFunc __all__ = ["evented"] diff --git a/src/psygnal/_evented_model.py b/src/psygnal/_evented_model.py index 4606a3e1..427c0384 100644 --- a/src/psygnal/_evented_model.py +++ b/src/psygnal/_evented_model.py @@ -1,17 +1,13 @@ import sys import warnings +from collections.abc import Iterator, Mapping from contextlib import contextmanager, suppress from typing import ( TYPE_CHECKING, Any, Callable, ClassVar, - Dict, - Iterator, - Mapping, NamedTuple, - Set, - Type, Union, cast, no_type_check, @@ -111,8 +107,8 @@ def _return2(x: str, y: "Signature") -> "Signature": if not PYDANTIC_V1: def _get_defaults( - obj: Union[pydantic.BaseModel, Type[pydantic.BaseModel]], - ) -> Dict[str, Any]: + obj: Union[pydantic.BaseModel, type[pydantic.BaseModel]], + ) -> dict[str, Any]: """Get possibly nested default values for a Model object.""" dflt = {} for k, v in obj.model_fields.items(): @@ -129,7 +125,7 @@ def _get_defaults( def _get_config(cls: pydantic.BaseModel) -> "ConfigDict": return cls.model_config - def _get_fields(cls: pydantic.BaseModel) -> Dict[str, pydantic.fields.FieldInfo]: + def _get_fields(cls: pydantic.BaseModel) -> dict[str, pydantic.fields.FieldInfo]: return cls.model_fields def _model_dump(obj: pydantic.BaseModel) -> dict: @@ -147,7 +143,7 @@ def _is_pydantic_descriptor_proxy(obj: Any) -> "TypeGuard[PydanticDescriptorProx else: @no_type_check - def _get_defaults(obj: pydantic.BaseModel) -> Dict[str, Any]: + def _get_defaults(obj: pydantic.BaseModel) -> dict[str, Any]: """Get possibly nested default values for a Model object.""" dflt = {} for k, v in obj.__fields__.items(): @@ -169,11 +165,11 @@ def _get_config(cls: type) -> "ConfigDict": return GetAttrAsItem(cls.__config__) class FieldInfo(NamedTuple): - annotation: Union[Type[Any], None] + annotation: Union[type[Any], None] frozen: Union[bool, None] @no_type_check - def _get_fields(cls: type) -> Dict[str, FieldInfo]: + def _get_fields(cls: type) -> dict[str, FieldInfo]: return { k: FieldInfo(annotation=f.type_, frozen=not f.field_info.allow_mutation) for k, f in cls.__fields__.items() @@ -214,7 +210,7 @@ class EventedMetaclass(pydantic_main.ModelMetaclass): when each instance of an ``EventedModel`` is instantiated). """ - __property_setters__: Dict[str, property] + __property_setters__: dict[str, property] @no_type_check def __new__( @@ -308,7 +304,7 @@ def __new__( def _get_field_dependents( cls: "EventedMetaclass", model_config: dict, model_fields: dict -) -> Dict[str, Set[str]]: +) -> dict[str, set[str]]: """Return mapping of field name -> dependent set of property names. Dependencies may be declared in the Model Config to emit an event @@ -332,7 +328,7 @@ def c(self, val: Sequence[int]): class Config: field_dependencies={'c': ['a', 'b']} """ - deps: Dict[str, Set[str]] = {} + deps: dict[str, set[str]] = {} cfg_deps = model_config.get(FIELD_DEPENDENCIES, {}) # sourcery skip if not cfg_deps: @@ -464,15 +460,15 @@ class Config: _events: ClassVar[SignalGroup] = PrivateAttr() # mapping of name -> property obj for methods that are property setters - __property_setters__: ClassVar[Dict[str, property]] + __property_setters__: ClassVar[dict[str, property]] # mapping of field name -> dependent set of property names # when field is changed, an event for dependent properties will be emitted. - __field_dependents__: ClassVar[Dict[str, Set[str]]] - __eq_operators__: ClassVar[Dict[str, "EqOperator"]] + __field_dependents__: ClassVar[dict[str, set[str]]] + __eq_operators__: ClassVar[dict[str, "EqOperator"]] __slots__ = {"__weakref__"} - __signal_group__: ClassVar[Type[SignalGroup]] - _changes_queue: Dict[str, Any] = PrivateAttr(default_factory=dict) - _primary_changes: Set[str] = PrivateAttr(default_factory=set) + __signal_group__: ClassVar[type[SignalGroup]] + _changes_queue: dict[str, Any] = PrivateAttr(default_factory=dict) + _primary_changes: set[str] = PrivateAttr(default_factory=set) _delay_check_semaphore: int = PrivateAttr(0) if PYDANTIC_V1: @@ -496,7 +492,7 @@ def events(self) -> SignalGroup: return self._events @property - def _defaults(self) -> Dict[str, Any]: + def _defaults(self) -> dict[str, Any]: return _get_defaults(self) def __eq__(self, other: Any) -> bool: diff --git a/src/psygnal/_exceptions.py b/src/psygnal/_exceptions.py index d4af9925..a043a849 100644 --- a/src/psygnal/_exceptions.py +++ b/src/psygnal/_exceptions.py @@ -3,11 +3,13 @@ import inspect from contextlib import suppress from pathlib import Path -from typing import TYPE_CHECKING, Any, Sequence +from typing import TYPE_CHECKING, Any import psygnal if TYPE_CHECKING: + from collections.abc import Sequence + from ._signal import SignalInstance diff --git a/src/psygnal/_group.py b/src/psygnal/_group.py index f50c2483..59154637 100644 --- a/src/psygnal/_group.py +++ b/src/psygnal/_group.py @@ -17,11 +17,7 @@ Any, Callable, ClassVar, - ContextManager, - Iterable, - Iterator, Literal, - Mapping, NamedTuple, overload, ) @@ -32,6 +28,8 @@ if TYPE_CHECKING: import threading + from collections.abc import Iterable, Iterator, Mapping + from contextlib import AbstractContextManager from psygnal._signal import F, ReducerFunc from psygnal._weak_callback import RefErrorChoice, WeakCallback @@ -175,7 +173,7 @@ def unblock(self) -> None: def blocked( self, exclude: Iterable[str | SignalInstance] = () - ) -> ContextManager[None]: + ) -> AbstractContextManager[None]: """Context manager to temporarily block all emitters in this group. Parameters @@ -526,7 +524,7 @@ def unblock(self) -> None: def blocked( self, exclude: Iterable[str | SignalInstance] = () - ) -> ContextManager[None]: + ) -> AbstractContextManager[None]: return self._psygnal_relay.blocked(exclude=exclude) def pause(self) -> None: @@ -537,7 +535,7 @@ def resume(self, reducer: ReducerFunc | None = None, initial: Any = _NULL) -> No def paused( self, reducer: ReducerFunc | None = None, initial: Any = _NULL - ) -> ContextManager[None]: + ) -> AbstractContextManager[None]: return self._psygnal_relay.paused(reducer=reducer, initial=initial) diff --git a/src/psygnal/_group_descriptor.py b/src/psygnal/_group_descriptor.py index bf789fdd..73b92255 100644 --- a/src/psygnal/_group_descriptor.py +++ b/src/psygnal/_group_descriptor.py @@ -11,11 +11,8 @@ Any, Callable, ClassVar, - Iterable, Literal, - Mapping, Optional, - Type, TypeVar, cast, overload, @@ -27,6 +24,7 @@ if TYPE_CHECKING: from _weakref import ref as ref + from collections.abc import Iterable, Mapping from typing_extensions import TypeAlias @@ -38,7 +36,7 @@ __all__ = ["is_evented", "get_evented_namespace", "SignalGroupDescriptor"] -T = TypeVar("T", bound=Type) +T = TypeVar("T", bound=type) S = TypeVar("S") diff --git a/src/psygnal/_pyinstaller_util/_pyinstaller_hook.py b/src/psygnal/_pyinstaller_util/_pyinstaller_hook.py index a1b0b28c..a0db5d96 100644 --- a/src/psygnal/_pyinstaller_util/_pyinstaller_hook.py +++ b/src/psygnal/_pyinstaller_util/_pyinstaller_hook.py @@ -1,8 +1,7 @@ from pathlib import Path -from typing import List CURRENT_DIR = Path(__file__).parent -def get_hook_dirs() -> List[str]: +def get_hook_dirs() -> list[str]: return [str(CURRENT_DIR)] diff --git a/src/psygnal/_pyinstaller_util/hook-psygnal.py b/src/psygnal/_pyinstaller_util/hook-psygnal.py index 63b5d7fc..d0e8a795 100644 --- a/src/psygnal/_pyinstaller_util/hook-psygnal.py +++ b/src/psygnal/_pyinstaller_util/hook-psygnal.py @@ -1,7 +1,8 @@ +from collections.abc import Iterable from importlib.metadata import PackageNotFoundError, PackagePath from importlib.metadata import files as package_files from pathlib import Path -from typing import Iterable, List, Union +from typing import Union try: import psygnal @@ -11,11 +12,11 @@ PSYGNAL_DIR = Path(__file__).parent.parent -def binary_files(file_list: Iterable[Union[PackagePath, Path]]) -> List[Path]: +def binary_files(file_list: Iterable[Union[PackagePath, Path]]) -> list[Path]: return [Path(file) for file in file_list if file.suffix in {".so", ".pyd"}] -def create_hiddenimports() -> List[str]: +def create_hiddenimports() -> list[str]: res = ["queue", "mypy_extensions", "__future__"] try: diff --git a/src/psygnal/_queue.py b/src/psygnal/_queue.py index 48e4564e..4093a8ce 100644 --- a/src/psygnal/_queue.py +++ b/src/psygnal/_queue.py @@ -1,18 +1,15 @@ from __future__ import annotations +from collections import defaultdict from queue import Queue from threading import Thread, current_thread, main_thread -from typing import TYPE_CHECKING, Any, Callable, ClassVar, DefaultDict, Literal, Tuple - -if TYPE_CHECKING: - import collections - +from typing import Any, Callable, ClassVar, Literal from ._exceptions import EmitLoopError from ._weak_callback import WeakCallback -Callback = Callable[[Tuple[Any, ...]], Any] -CbArgsTuple = Tuple[Callback, tuple] +Callback = Callable[[tuple[Any, ...]], Any] +CbArgsTuple = tuple[Callback, tuple] class QueuedCallback(WeakCallback): @@ -29,8 +26,8 @@ class QueuedCallback(WeakCallback): thread will be used. """ - _GLOBAL_QUEUE: ClassVar[collections.defaultdict[Thread, Queue[CbArgsTuple]]] = ( - DefaultDict(Queue) + _GLOBAL_QUEUE: ClassVar[defaultdict[Thread, Queue[CbArgsTuple]]] = defaultdict( + Queue ) def __init__( diff --git a/src/psygnal/_signal.py b/src/psygnal/_signal.py index 6e285b18..d4cea1e5 100644 --- a/src/psygnal/_signal.py +++ b/src/psygnal/_signal.py @@ -133,21 +133,17 @@ def ensure_at_least_20(val: int): import warnings import weakref from collections import deque -from contextlib import contextmanager, suppress -from functools import lru_cache, partial, reduce +from contextlib import AbstractContextManager, contextmanager, suppress +from functools import cache, partial, reduce from inspect import Parameter, Signature, isclass from typing import ( TYPE_CHECKING, Any, Callable, ClassVar, - ContextManager, Final, - Iterable, - Iterator, Literal, NoReturn, - Type, TypeVar, Union, cast, @@ -169,6 +165,8 @@ def ensure_at_least_20(val: int): ) if TYPE_CHECKING: + from collections.abc import Iterable, Iterator + from ._group import EmissionInfo from ._weak_callback import RefErrorChoice @@ -314,7 +312,7 @@ def __init__( stacklevel=2, ) else: - self._signature = _build_signature(*cast("tuple[Type[Any], ...]", types)) + self._signature = _build_signature(*cast("tuple[type[Any], ...]", types)) @property def signature(self) -> Signature: @@ -1323,7 +1321,7 @@ def unblock(self) -> None: """Unblock this signal, allowing it to emit.""" self._is_blocked = False - def blocked(self) -> ContextManager[None]: + def blocked(self) -> AbstractContextManager[None]: """Context manager to temporarily block this signal. Useful if you need to temporarily block all emission of a given signal, @@ -1414,7 +1412,7 @@ def resume(self, reducer: ReducerFunc | None = None, initial: Any = _NULL) -> No def paused( self, reducer: ReducerFunc | None = None, initial: Any = _NULL - ) -> ContextManager[None]: + ) -> AbstractContextManager[None]: """Context manager to temporarily pause this signal. Parameters @@ -1550,7 +1548,7 @@ def signature(obj: Any) -> inspect.Signature: ) -@lru_cache(maxsize=None) +@cache def _stub_sig(obj: Any) -> Signature: """Called as a backup when inspect.signature fails.""" import builtins diff --git a/src/psygnal/containers/_evented_dict.py b/src/psygnal/containers/_evented_dict.py index 4c2b3962..5f60338e 100644 --- a/src/psygnal/containers/_evented_dict.py +++ b/src/psygnal/containers/_evented_dict.py @@ -2,17 +2,11 @@ from __future__ import annotations +from collections.abc import Iterable, Iterator, Mapping, MutableMapping, Sequence from typing import ( TYPE_CHECKING, Any, Callable, - Iterable, - Iterator, - Mapping, - MutableMapping, - Sequence, - Tuple, - Type, TypeVar, Union, get_args, @@ -26,8 +20,8 @@ _K = TypeVar("_K") _V = TypeVar("_V") -TypeOrSequenceOfTypes = Union[Type[_V], Sequence[Type[_V]]] -DictArg = Union[Mapping[_K, _V], Iterable[Tuple[_K, _V]]] +TypeOrSequenceOfTypes = Union[type[_V], Sequence[type[_V]]] +DictArg = Union[Mapping[_K, _V], Iterable[tuple[_K, _V]]] class TypedMutableMapping(MutableMapping[_K, _V]): diff --git a/src/psygnal/containers/_evented_list.py b/src/psygnal/containers/_evented_list.py index 7cea7d1c..e72a647f 100644 --- a/src/psygnal/containers/_evented_list.py +++ b/src/psygnal/containers/_evented_list.py @@ -24,13 +24,11 @@ from __future__ import annotations +from collections.abc import Iterable, Mapping, MutableSequence from typing import ( TYPE_CHECKING, Any, Callable, - Iterable, - Mapping, - MutableSequence, TypeVar, Union, cast, diff --git a/src/psygnal/containers/_evented_proxy.py b/src/psygnal/containers/_evented_proxy.py index 46342c76..fcd5f57b 100644 --- a/src/psygnal/containers/_evented_proxy.py +++ b/src/psygnal/containers/_evented_proxy.py @@ -1,5 +1,5 @@ from functools import partial -from typing import Any, Callable, Dict, Generic, List, TypeVar +from typing import Any, Callable, Generic, TypeVar from weakref import finalize try: @@ -35,7 +35,7 @@ class CallableProxyEvents(ProxyEvents): # we're using a cache instead of setting the events object directly on the proxy # because when wrapt is compiled as a C extensions, the ObjectProxy is not allowed # to add any new attributes. -_OBJ_CACHE: Dict[int, ProxyEvents] = {} +_OBJ_CACHE: dict[int, ProxyEvents] = {} class EventedObjectProxy(ObjectProxy, Generic[T]): @@ -102,7 +102,7 @@ def __delitem__(self, key: Any) -> None: def __repr__(self) -> str: return repr(self.__wrapped__) - def __dir__(self) -> List[str]: + def __dir__(self) -> list[str]: return [*dir(self.__wrapped__), "events"] def __iadd__(self, other: Any) -> T: diff --git a/src/psygnal/containers/_evented_set.py b/src/psygnal/containers/_evented_set.py index 9559b826..2c841c82 100644 --- a/src/psygnal/containers/_evented_set.py +++ b/src/psygnal/containers/_evented_set.py @@ -1,16 +1,13 @@ from __future__ import annotations import inspect +from collections.abc import Iterable, Iterator, Mapping, MutableSet from itertools import chain from typing import ( TYPE_CHECKING, Any, Callable, Final, - Iterable, - Iterator, - Mapping, - MutableSet, TypeVar, get_args, ) diff --git a/src/psygnal/containers/_selectable_evented_list.py b/src/psygnal/containers/_selectable_evented_list.py index d44ca695..bebe8da2 100644 --- a/src/psygnal/containers/_selectable_evented_list.py +++ b/src/psygnal/containers/_selectable_evented_list.py @@ -1,6 +1,7 @@ """MutableSequence with a selection model.""" -from typing import Any, Iterable, Tuple, TypeVar +from collections.abc import Iterable +from typing import Any, TypeVar from ._evented_list import EventedList, ListEvents from ._selection import Selectable @@ -106,7 +107,7 @@ def select_previous( step=-1, expand_selection=expand_selection, wraparound=wraparound ) - def remove_selected(self) -> Tuple[_T, ...]: + def remove_selected(self) -> tuple[_T, ...]: """Remove selected items from the list and the selection. Returns diff --git a/src/psygnal/containers/_selection.py b/src/psygnal/containers/_selection.py index 7aa32640..7650a160 100644 --- a/src/psygnal/containers/_selection.py +++ b/src/psygnal/containers/_selection.py @@ -1,13 +1,14 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Container, TypeVar +from collections.abc import Container +from typing import TYPE_CHECKING, Any, TypeVar from psygnal._signal import Signal from ._evented_set import BailType, EventedOrderedSet, SetEvents if TYPE_CHECKING: - from typing import Iterable + from collections.abc import Iterable _T = TypeVar("_T") _S = TypeVar("_S") diff --git a/src/psygnal/utils.py b/src/psygnal/utils.py index c84f6482..69a73147 100644 --- a/src/psygnal/utils.py +++ b/src/psygnal/utils.py @@ -5,12 +5,15 @@ from contextlib import contextmanager, suppress from functools import partial from pathlib import Path -from typing import Any, Callable, Generator, Iterator +from typing import TYPE_CHECKING, Any, Callable from warnings import warn from ._group import EmissionInfo, SignalGroup from ._signal import SignalInstance +if TYPE_CHECKING: + from collections.abc import Generator, Iterator + __all__ = ["monitor_events", "iter_signal_instances"] diff --git a/tests/containers/test_evented_list.py b/tests/containers/test_evented_list.py index 1aee54f6..333d1441 100644 --- a/tests/containers/test_evented_list.py +++ b/tests/containers/test_evented_list.py @@ -1,6 +1,6 @@ import os from copy import copy -from typing import List, cast +from typing import cast from unittest.mock import Mock, call import numpy as np @@ -220,7 +220,7 @@ def _fail() -> None: assert test_list == [3, 2, 0, 1, 4] -BASIC_INDICES: List[tuple] = [ +BASIC_INDICES: list[tuple] = [ ((2,), 0, [2, 0, 1, 3, 4, 5, 6, 7]), # move single item ([0, 2, 3], 6, [1, 4, 5, 0, 2, 3, 6, 7]), # move back ([4, 7], 1, [0, 4, 7, 1, 2, 3, 5, 6]), # move forward @@ -228,7 +228,7 @@ def _fail() -> None: ([1, 3, 5, 7], 3, [0, 2, 1, 3, 5, 7, 4, 6]), # same as above ([0, 2, 3, 2, 3], 6, [1, 4, 5, 0, 2, 3, 6, 7]), # strip dupe indices ] -OTHER_INDICES: List[tuple] = [ +OTHER_INDICES: list[tuple] = [ ([7, 4], 1, [0, 7, 4, 1, 2, 3, 5, 6]), # move forward reorder ([3, 0, 2], 6, [1, 4, 5, 3, 0, 2, 6, 7]), # move back reorder ((2, 4), -2, [0, 1, 3, 5, 6, 2, 4, 7]), # negative indexing diff --git a/tests/test_evented_model.py b/tests/test_evented_model.py index 125b9b0a..12b41773 100644 --- a/tests/test_evented_model.py +++ b/tests/test_evented_model.py @@ -1,7 +1,8 @@ import inspect import sys +from collections.abc import Sequence from contextlib import nullcontext -from typing import Any, ClassVar, List, Protocol, Sequence, Union, runtime_checkable +from typing import Any, ClassVar, Protocol, Union, runtime_checkable from unittest.mock import Mock, call, patch import numpy as np @@ -422,7 +423,7 @@ class T(EventedModel): b: int = 1 @property - def c(self) -> List[int]: + def c(self) -> list[int]: return [self.a, self.b] @c.setter @@ -485,7 +486,7 @@ class MyModel(EventedModel): b: int = 1 @property - def c(self) -> List[int]: + def c(self) -> list[int]: return [self.a, self.b] @c.setter @@ -914,9 +915,9 @@ def d(self, value): ) def test_evented_model_reemission(mode: Union[str, dict]) -> None: err = mode == "err" or isinstance(mode, dict) and "err" in mode.values() - with pytest.raises( - ValueError, match="Invalid reemission" - ) if err else nullcontext(): + with ( + pytest.raises(ValueError, match="Invalid reemission") if err else nullcontext() + ): class Model(EventedModel): a: int @@ -951,7 +952,7 @@ class MyModel(EventedModel): @computed_field @property - def c(self) -> List[int]: + def c(self) -> list[int]: return [self.a, self.b] @c.setter diff --git a/tests/test_group_descriptor.py b/tests/test_group_descriptor.py index 98270b05..54527c1e 100644 --- a/tests/test_group_descriptor.py +++ b/tests/test_group_descriptor.py @@ -1,6 +1,6 @@ from contextlib import nullcontext from dataclasses import dataclass -from typing import Any, ClassVar, Optional, Type +from typing import Any, ClassVar, Optional from unittest.mock import Mock, patch import pytest @@ -228,7 +228,7 @@ class Bar: @pytest.mark.parametrize("collect", [True, False]) @pytest.mark.parametrize("klass", [None, SignalGroup, MyGroup]) -def test_collect_fields(collect: bool, klass: Optional[Type[SignalGroup]]) -> None: +def test_collect_fields(collect: bool, klass: Optional[type[SignalGroup]]) -> None: signal_class = klass or SignalGroup should_fail_def = signal_class is SignalGroup and collect is False ctx = pytest.raises(ValueError) if should_fail_def else nullcontext() diff --git a/tests/test_qt_compat.py b/tests/test_qt_compat.py index 5395e76b..0cf75ee2 100644 --- a/tests/test_qt_compat.py +++ b/tests/test_qt_compat.py @@ -1,7 +1,7 @@ """qtbot should work for testing!""" from threading import Thread, current_thread, main_thread -from typing import TYPE_CHECKING, Any, Callable, Literal, Tuple +from typing import TYPE_CHECKING, Any, Callable, Literal from unittest.mock import Mock import pytest @@ -14,7 +14,7 @@ from pytestqt.qtbot import QtBot -def _equals(*val: Any) -> Callable[[Tuple[Any, ...]], bool]: +def _equals(*val: Any) -> Callable[[tuple[Any, ...]], bool]: def _inner(*other: Any) -> bool: return other == val