diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 34b27269..5e9535c7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,7 +33,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] os: [ubuntu-latest, macos-latest, windows-latest] compile: [true, false] include: @@ -44,12 +44,9 @@ jobs: python-version: "3.11" pydantic: "'pydantic<2'" exclude: + # still working on ci test errors - os: windows-latest python-version: "3.12" - - os: windows-latest - python-version: "3.7" - - os: macos-latest - python-version: "3.7" test-qt: name: Test Qt diff --git a/pyproject.toml b/pyproject.toml index d9cc5c4d..f03d3d8b 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.7" +requires-python = ">=3.8" 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.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", @@ -25,11 +24,7 @@ classifiers = [ "Typing :: Typed", ] dynamic = ["version"] -dependencies = [ - "typing-extensions >=3.7.4.2", - "mypy_extensions", - "importlib_metadata ; python_version < '3.8'", -] +dependencies = ["typing-extensions >=3.7.4.2", "mypy_extensions"] # extras # https://peps.python.org/pep-0621/#dependencies-optional-dependencies @@ -122,7 +117,7 @@ exclude = [ [tool.cibuildwheel] # Skip 32-bit builds & PyPy wheels on all platforms skip = ["*-manylinux_i686", "*-musllinux_i686", "*-win32", "pp*"] -build = ["cp37-*", "cp38-*", "cp39-*", "cp310-*", "cp311-*"] +build = ["cp38-*", "cp39-*", "cp310-*", "cp311-*", "cp312-*"] test-extras = ["test"] test-command = "pytest {project}/tests -v" test-skip = "*-musllinux*" @@ -196,15 +191,9 @@ module = ["numpy.*", "wrapt", "pydantic.*"] ignore_errors = true [[tool.mypy.overrides]] -# msgspec is only available on Python 3.8+ ... so we need to ignore it -module = ["wrapt", "msgspec"] +module = ["wrapt"] ignore_missing_imports = true -# remove when dropping python 3.7 -[[tool.mypy.overrides]] -module = ["psygnal._signal"] -warn_unused_ignores = false - # https://coverage.readthedocs.io/en/6.4/config.html [tool.coverage.report] exclude_lines = [ diff --git a/src/psygnal/__init__.py b/src/psygnal/__init__.py index 28c6ac98..fd2dfe36 100644 --- a/src/psygnal/__init__.py +++ b/src/psygnal/__init__.py @@ -4,22 +4,12 @@ """ import os +from importlib.metadata import PackageNotFoundError, version from typing import TYPE_CHECKING, Any if TYPE_CHECKING: - PackageNotFoundError = Exception from ._evented_model_v1 import EventedModel # noqa: TCH004 - def version(package: str) -> str: - """Return version.""" - -else: - # hiding this import from type checkers so mypyc can work on both 3.7 and later - try: - from importlib.metadata import PackageNotFoundError, version - except ImportError: - from importlib_metadata import PackageNotFoundError, version - try: __version__ = version("psygnal") diff --git a/src/psygnal/_dataclass_utils.py b/src/psygnal/_dataclass_utils.py index 56c49544..ed8a9ab3 100644 --- a/src/psygnal/_dataclass_utils.py +++ b/src/psygnal/_dataclass_utils.py @@ -168,8 +168,7 @@ def iter_fields( """ # generally opting for speed here over public API - dclass_fields = getattr(cls, "__dataclass_fields__", None) - if dclass_fields is not None: + if (dclass_fields := getattr(cls, "__dataclass_fields__", None)) is not None: for d_field in dclass_fields.values(): if d_field._field_type is dataclasses._FIELD: # type: ignore [attr-defined] yield d_field.name, d_field.type @@ -186,8 +185,7 @@ def iter_fields( yield p_field.name, p_field.outer_type_ # type: ignore return - attrs_fields = getattr(cls, "__attrs_attrs__", None) - if attrs_fields is not None: + if (attrs_fields := getattr(cls, "__attrs_attrs__", None)) is not None: for a_field in attrs_fields: yield a_field.name, a_field.type return diff --git a/src/psygnal/_group.py b/src/psygnal/_group.py index cd07a2e0..bed9b8f0 100644 --- a/src/psygnal/_group.py +++ b/src/psygnal/_group.py @@ -130,8 +130,7 @@ def is_uniform(cls) -> bool: return cls._uniform def _slot_relay(self, *args: Any) -> None: - emitter = Signal.current_emitter() - if emitter: + if emitter := Signal.current_emitter(): info = EmissionInfo(emitter, args) self._run_emit_loop((info,)) diff --git a/src/psygnal/_group_descriptor.py b/src/psygnal/_group_descriptor.py index 46535585..682ea057 100644 --- a/src/psygnal/_group_descriptor.py +++ b/src/psygnal/_group_descriptor.py @@ -440,11 +440,8 @@ def __get__( setattr(instance, self._name, self._instance_map[obj_id]) # clean up the cache when the instance is deleted - with contextlib.suppress(TypeError): - # on 3.7 this is type error, above it's not... but mypy yells about - # type ignore on 3.8+, so we do this funny business instead. - args = (instance, self._instance_map.pop, obj_id, None) - weakref.finalize(*args) # type: ignore + with contextlib.suppress(TypeError): # if it's not weakref-able + weakref.finalize(instance, self._instance_map.pop, obj_id, None) return self._instance_map[obj_id] diff --git a/src/psygnal/_signal.py b/src/psygnal/_signal.py index 489fafde..d5460455 100644 --- a/src/psygnal/_signal.py +++ b/src/psygnal/_signal.py @@ -493,9 +493,7 @@ def _wrapper( extra = f"- Slot types {slot_sig} do not match types in signal." self._raise_connection_error(slot, extra) - # this type ignore is only needed to build mypyc on pythong 3.7 - # can be removed when we drop support for 3.7 - cb = weak_callback( # type: ignore + cb = weak_callback( slot, max_args=max_args, finalize=self._try_discard, @@ -774,9 +772,8 @@ def _check_nargs( ) from e raise - n_spec_params = len(spec.parameters) # if `slot` requires more arguments than we will provide, raise. - if minargs > n_spec_params: + if minargs > (n_spec_params := len(spec.parameters)): extra = ( f"- Slot requires at least {minargs} positional " f"arguments, but spec only provides {n_spec_params}" diff --git a/src/psygnal/_weak_callback.py b/src/psygnal/_weak_callback.py index 87ebf89c..58767803 100644 --- a/src/psygnal/_weak_callback.py +++ b/src/psygnal/_weak_callback.py @@ -281,10 +281,12 @@ def object_repr(obj: Any) -> str: return f"{module}.{obj.__qualname__}" elif getattr(type(obj), "__qualname__", ""): return f"{module}.{type(obj).__qualname__}" - return repr(obj) + # this line was hit in py3.7, but not afterwards. + # retained as a fallback, but not covered by tests. + return repr(obj) # pragma: no cover def __repr__(self) -> str: - return f"<{self.__class__.__name__} on {self._object_repr}>" + return f"<{self.__class__.__name__} on {self._object_repr}>" # pragma: no cover def _kill_and_finalize( diff --git a/src/psygnal/containers/_evented_proxy.py b/src/psygnal/containers/_evented_proxy.py index 5a051350..46342c76 100644 --- a/src/psygnal/containers/_evented_proxy.py +++ b/src/psygnal/containers/_evented_proxy.py @@ -82,8 +82,7 @@ def events(self) -> ProxyEvents: # pragma: no cover # unclear why def __setattr__(self, name: str, value: None) -> None: before = getattr(self, name, _UNSET) super().__setattr__(name, value) - after = getattr(self, name, _UNSET) - if before is not after: + if before is not (after := getattr(self, name, _UNSET)): self.events.attribute_set(name, after) def __delattr__(self, name: str) -> None: @@ -93,8 +92,7 @@ def __delattr__(self, name: str) -> None: def __setitem__(self, key: Any, value: Any) -> None: before = self[key] super().__setitem__(key, value) - after = self[key] - if before is not after: + if before is not (after := self[key]): self.events.item_set(key, after) def __delitem__(self, key: Any) -> None: diff --git a/src/psygnal/qt.py b/src/psygnal/qt.py index ee53ecc3..61730a10 100644 --- a/src/psygnal/qt.py +++ b/src/psygnal/qt.py @@ -71,6 +71,5 @@ def stop_emitting_from_queue(thread: Thread | None = None) -> None: in the thread from which this function is called. """ _thread = current_thread() if thread is None else thread - timer = _TIMERS.get(_thread) - if timer is not None: + if (timer := _TIMERS.get(_thread)) is not None: timer.stop() diff --git a/tests/test_evented_model.py b/tests/test_evented_model.py index cfa6f4eb..774206d5 100644 --- a/tests/test_evented_model.py +++ b/tests/test_evented_model.py @@ -90,7 +90,6 @@ class User(EventedModel): name_mock.assert_not_called() -@pytest.mark.skipif(sys.version_info < (3, 8), reason="requires python3.8 or higher") def test_evented_model_array_updates(): """Test updating an evented pydantic model with an array.""" diff --git a/tests/test_psygnal.py b/tests/test_psygnal.py index 5fd51b36..05536d1f 100644 --- a/tests/test_psygnal.py +++ b/tests/test_psygnal.py @@ -1,5 +1,4 @@ import gc -import sys import time from contextlib import suppress from functools import partial, wraps @@ -846,12 +845,7 @@ def test_emit_loop_exceptions(): "f_any_assigned", "partial", "partial_kwargs", - pytest.param( - "partial", - marks=pytest.mark.xfail( - sys.version_info < (3, 8), reason="no idea why this fails on 3.7" - ), - ), + "partial", ], ) def test_weakref_disconnect(slot):