From ba28ac87006784ae0fbcafe2d118491d70f6dfb0 Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Fri, 15 Nov 2024 13:49:39 -0500 Subject: [PATCH] build: drop 3.8 and add 3.13 --- .github/workflows/ci.yml | 17 ++--------------- demo/model_app.py | 3 +-- demo/multi_file/actions.py | 4 +--- pyproject.toml | 6 +++--- src/app_model/_app.py | 19 +++++++------------ src/app_model/backends/qt/_qaction.py | 4 +++- src/app_model/backends/qt/_qkeymap.py | 4 +++- src/app_model/backends/qt/_qmainwindow.py | 5 ++++- src/app_model/backends/qt/_qmenu.py | 3 ++- src/app_model/expressions/_context.py | 4 +++- src/app_model/expressions/_context_keys.py | 2 +- src/app_model/expressions/_expressions.py | 12 +++--------- src/app_model/registries/_commands_reg.py | 4 +++- src/app_model/registries/_keybindings_reg.py | 3 ++- src/app_model/registries/_menus_reg.py | 4 +++- src/app_model/types/_action.py | 6 +++--- src/app_model/types/_icon.py | 3 ++- src/app_model/types/_keybinding_rule.py | 4 ++-- src/app_model/types/_keys/_keybindings.py | 11 ++++++----- src/app_model/types/_menu_rule.py | 7 +++---- tests/conftest.py | 3 ++- tests/test_actions.py | 4 +--- 22 files changed, 60 insertions(+), 72 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 53ce1f79..36605118 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,25 +25,15 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.10", "3.12"] + python-version: ["3.9", "3.10", "3.12", "3.13"] os: [ubuntu-latest, macos-latest, windows-latest] pydantic: [""] - exclude: - - python-version: "3.8" - os: "macos-latest" include: - - python-version: "3.8" - os: "macos-13" - - python-version: "3.9" - os: "macos-13" - python-version: "3.11" os: "ubuntu-latest" - python-version: "3.12" os: "ubuntu-latest" pydantic: "'pydantic<2'" - - python-version: "3.8" - os: "ubuntu-latest" - pydantic: "'pydantic<2'" test-qt: uses: pyapp-kit/workflows/.github/workflows/test-pyrepo.yml@v2 @@ -58,9 +48,6 @@ jobs: fail-fast: false matrix: include: - - python-version: "3.8" - os: "ubuntu-latest" - qt: "PyQt5==5.12" - python-version: "3.10" os: "ubuntu-latest" qt: "PyQt5~=5.15.0" @@ -82,7 +69,7 @@ jobs: - python-version: "3.11" os: "ubuntu-latest" qt: "PySide6~=6.6.0" - - python-version: "3.11" + - python-version: "3.13" os: "ubuntu-latest" qt: pyqt6 - python-version: "3.10" diff --git a/demo/model_app.py b/demo/model_app.py index f62c8972..a524fd81 100644 --- a/demo/model_app.py +++ b/demo/model_app.py @@ -1,5 +1,4 @@ from pathlib import Path -from typing import List from qtpy.QtCore import QFile, QFileInfo, QSaveFile, Qt, QTextStream from qtpy.QtWidgets import QApplication, QFileDialog, QMessageBox, QTextEdit @@ -160,7 +159,7 @@ class CommandId: ABOUT_ICON_PATH = Path(__file__).parent / "images" / "about.svg" -ACTIONS: List[types.Action] = [ +ACTIONS: list[types.Action] = [ types.Action( id="new_file", icon="fa6-solid:file-circle-plus", diff --git a/demo/multi_file/actions.py b/demo/multi_file/actions.py index 2cf508bf..78fb27ba 100644 --- a/demo/multi_file/actions.py +++ b/demo/multi_file/actions.py @@ -1,11 +1,9 @@ -from typing import List - from app_model.types import Action, KeyBindingRule, KeyCode, KeyMod, MenuRule from . import functions from .constants import CommandId, MenuId -ACTIONS: List[Action] = [ +ACTIONS: list[Action] = [ Action( id=CommandId.OPEN, title="Open", diff --git a/pyproject.toml b/pyproject.toml index 4ecf5140..f9cee957 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ build-backend = "hatchling.build" name = "app-model" description = "Generic application schema implemented in python" 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" }] classifiers = [ @@ -16,11 +16,11 @@ classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Desktop Environment", "Topic :: Software Development", "Topic :: Software Development :: User Interfaces", @@ -81,7 +81,7 @@ src_paths = ["src/app_model", "tests"] [tool.ruff] line-length = 88 src = ["src", "tests"] -target-version = "py38" +target-version = "py39" [tool.ruff.lint] pydocstyle = { convention = "numpy" } diff --git a/src/app_model/_app.py b/src/app_model/_app.py index 3d342f5e..65507ebd 100644 --- a/src/app_model/_app.py +++ b/src/app_model/_app.py @@ -3,17 +3,12 @@ import contextlib import os import sys +from collections.abc import Iterable, MutableMapping from typing import ( TYPE_CHECKING, ClassVar, - Dict, - Iterable, - List, Literal, - MutableMapping, Optional, - Tuple, - Type, overload, ) @@ -85,17 +80,17 @@ class Application: """ destroyed = Signal(str) - _instances: ClassVar[Dict[str, Application]] = {} + _instances: ClassVar[dict[str, Application]] = {} def __init__( self, name: str, *, raise_synchronous_exceptions: bool = False, - commands_reg_class: Type[CommandsRegistry] = CommandsRegistry, - menus_reg_class: Type[MenusRegistry] = MenusRegistry, - keybindings_reg_class: Type[KeyBindingsRegistry] = KeyBindingsRegistry, - injection_store_class: Type[ino.Store] = ino.Store, + commands_reg_class: type[CommandsRegistry] = CommandsRegistry, + menus_reg_class: type[MenusRegistry] = MenusRegistry, + keybindings_reg_class: type[KeyBindingsRegistry] = KeyBindingsRegistry, + injection_store_class: type[ino.Store] = ino.Store, context: Context | MutableMapping | None = None, ) -> None: self._name = name @@ -131,7 +126,7 @@ def __init__( self.injection_store.on_unannotated_required_args = "ignore" - self._disposers: List[Tuple[str, DisposeCallable]] = [] + self._disposers: list[tuple[str, DisposeCallable]] = [] @property def raise_synchronous_exceptions(self) -> bool: diff --git a/src/app_model/backends/qt/_qaction.py b/src/app_model/backends/qt/_qaction.py index ccf11757..e6753d26 100644 --- a/src/app_model/backends/qt/_qaction.py +++ b/src/app_model/backends/qt/_qaction.py @@ -1,7 +1,7 @@ from __future__ import annotations import contextlib -from typing import TYPE_CHECKING, ClassVar, Mapping +from typing import TYPE_CHECKING, ClassVar from weakref import WeakValueDictionary from app_model import Application @@ -12,6 +12,8 @@ from ._util import to_qicon if TYPE_CHECKING: + from collections.abc import Mapping + from PyQt6.QtGui import QAction from qtpy.QtCore import QObject from typing_extensions import Self diff --git a/src/app_model/backends/qt/_qkeymap.py b/src/app_model/backends/qt/_qkeymap.py index d2b0d583..5d8638cc 100644 --- a/src/app_model/backends/qt/_qkeymap.py +++ b/src/app_model/backends/qt/_qkeymap.py @@ -3,7 +3,7 @@ import operator from functools import reduce -from typing import TYPE_CHECKING, Mapping, MutableMapping +from typing import TYPE_CHECKING from qtpy import API, QT_VERSION from qtpy.QtCore import QCoreApplication, Qt @@ -19,6 +19,8 @@ from app_model.types._constants import OperatingSystem if TYPE_CHECKING: + from collections.abc import Mapping, MutableMapping + from qtpy.QtCore import QKeyCombination try: diff --git a/src/app_model/backends/qt/_qmainwindow.py b/src/app_model/backends/qt/_qmainwindow.py index 659d09cd..23b26270 100644 --- a/src/app_model/backends/qt/_qmainwindow.py +++ b/src/app_model/backends/qt/_qmainwindow.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Collection, Mapping, Sequence +from typing import TYPE_CHECKING from qtpy.QtCore import Qt from qtpy.QtWidgets import QMainWindow, QWidget @@ -9,6 +9,9 @@ from ._qmenu import QModelMenuBar, QModelToolBar +if TYPE_CHECKING: + from collections.abc import Collection, Mapping, Sequence + class QModelMainWindow(QMainWindow): """QMainWindow with app-model support.""" diff --git a/src/app_model/backends/qt/_qmenu.py b/src/app_model/backends/qt/_qmenu.py index 0ddc4068..11f6cdc0 100644 --- a/src/app_model/backends/qt/_qmenu.py +++ b/src/app_model/backends/qt/_qmenu.py @@ -1,7 +1,8 @@ from __future__ import annotations import contextlib -from typing import TYPE_CHECKING, Collection, Iterable, Mapping, Sequence, cast +from collections.abc import Collection, Iterable, Mapping, Sequence +from typing import TYPE_CHECKING, cast from qtpy.QtWidgets import QMenu, QMenuBar, QToolBar diff --git a/src/app_model/expressions/_context.py b/src/app_model/expressions/_context.py index 168b1209..3d938847 100644 --- a/src/app_model/expressions/_context.py +++ b/src/app_model/expressions/_context.py @@ -2,13 +2,15 @@ import os import sys +from collections import ChainMap from contextlib import contextmanager -from typing import TYPE_CHECKING, Any, Callable, ChainMap, Iterator, MutableMapping +from typing import TYPE_CHECKING, Any, Callable from weakref import finalize from psygnal import Signal if TYPE_CHECKING: + from collections.abc import Iterator, MutableMapping from types import FrameType from typing import TypedDict diff --git a/src/app_model/expressions/_context_keys.py b/src/app_model/expressions/_context_keys.py index 67fae875..6d323f2e 100644 --- a/src/app_model/expressions/_context_keys.py +++ b/src/app_model/expressions/_context_keys.py @@ -9,7 +9,6 @@ ClassVar, Generic, Literal, - MutableMapping, NamedTuple, TypeVar, overload, @@ -19,6 +18,7 @@ if TYPE_CHECKING: import builtins + from collections.abc import MutableMapping T = TypeVar("T") A = TypeVar("A") diff --git a/src/app_model/expressions/_expressions.py b/src/app_model/expressions/_expressions.py index c58f2146..89889062 100644 --- a/src/app_model/expressions/_expressions.py +++ b/src/app_model/expressions/_expressions.py @@ -8,11 +8,7 @@ Any, Callable, Generic, - Iterator, - Mapping, - Sequence, SupportsIndex, - Type, TypeVar, Union, cast, @@ -29,6 +25,7 @@ V = TypeVar("V", bound=ConstType) if TYPE_CHECKING: + from collections.abc import Iterator, Mapping, Sequence from types import CodeType from pydantic.annotated_handlers import GetCoreSchemaHandler @@ -507,10 +504,7 @@ class Set(Expr, ast.Set): `elts` is a list of expressions. """ - def __init__( - self, elts: Sequence[Expr], ctx: ast.expr_context = LOAD, **kwargs: Any - ) -> None: - kwargs["ctx"] = ctx + def __init__(self, elts: Sequence[Expr], **kwargs: Any) -> None: super().__init__(elts=[Expr._cast(e) for e in elts], **kwargs) @@ -643,7 +637,7 @@ def visit_IfExp(self, node: ast.IfExp) -> Any: self.write(node.body, " if ", node.test, " else ", node.orelse) -OpType = Union[Type[ast.operator], Type[ast.cmpop], Type[ast.boolop], Type[ast.unaryop]] +OpType = Union[type[ast.operator], type[ast.cmpop], type[ast.boolop], type[ast.unaryop]] _OPS: dict[OpType, str] = { # ast.boolop ast.Or: "or", diff --git a/src/app_model/registries/_commands_reg.py b/src/app_model/registries/_commands_reg.py index d45448e9..a719972b 100644 --- a/src/app_model/registries/_commands_reg.py +++ b/src/app_model/registries/_commands_reg.py @@ -1,13 +1,15 @@ from __future__ import annotations from concurrent.futures import Future, ThreadPoolExecutor -from typing import TYPE_CHECKING, Any, Callable, Generic, Iterator, TypeVar, cast +from typing import TYPE_CHECKING, Any, Callable, Generic, TypeVar, cast from in_n_out import Store from psygnal import Signal # maintain runtime compatibility with older typing_extensions if TYPE_CHECKING: + from collections.abc import Iterator + from typing_extensions import ParamSpec from app_model.types import Action, DisposeCallable diff --git a/src/app_model/registries/_keybindings_reg.py b/src/app_model/registries/_keybindings_reg.py index 318383cf..baec0bbe 100644 --- a/src/app_model/registries/_keybindings_reg.py +++ b/src/app_model/registries/_keybindings_reg.py @@ -7,7 +7,8 @@ from app_model.types import KeyBinding if TYPE_CHECKING: - from typing import Iterator, TypeVar + from collections.abc import Iterator + from typing import TypeVar from app_model import expressions from app_model.types import Action, DisposeCallable, KeyBindingRule diff --git a/src/app_model/registries/_menus_reg.py b/src/app_model/registries/_menus_reg.py index 5134e372..b0929335 100644 --- a/src/app_model/registries/_menus_reg.py +++ b/src/app_model/registries/_menus_reg.py @@ -1,12 +1,14 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Final, Iterable, Iterator +from typing import TYPE_CHECKING, Any, Callable, Final from psygnal import Signal from app_model.types import MenuItem if TYPE_CHECKING: + from collections.abc import Iterable, Iterator + from app_model.types import Action, DisposeCallable, MenuOrSubmenu MenuId = str diff --git a/src/app_model/types/_action.py b/src/app_model/types/_action.py index 52748d90..ce5ee6d2 100644 --- a/src/app_model/types/_action.py +++ b/src/app_model/types/_action.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Callable, Generic, List, Optional, TypeVar, Union +from typing import TYPE_CHECKING, Callable, Generic, Optional, TypeVar, Union from pydantic_compat import Field, field_validator @@ -39,13 +39,13 @@ class Action(CommandRule, Generic[P, R]): "`{obj.__module__}:{obj.__qualname__}` " "(e.g. `my_package.a_module:some_function`)", ) - menus: Optional[List[MenuRule]] = Field( + menus: Optional[list[MenuRule]] = Field( None, description="(Optional) Menus to which this action should be added. Note that " "menu items in the sequence may be supplied as a plain string, which will " "be converted to a `MenuRule` with the string as the `id` field.", ) - keybindings: Optional[List[KeyBindingRule]] = Field( + keybindings: Optional[list[KeyBindingRule]] = Field( None, description="(Optional) Default keybinding(s) that will trigger this command.", ) diff --git a/src/app_model/types/_icon.py b/src/app_model/types/_icon.py index 48ccd46d..1dc578f0 100644 --- a/src/app_model/types/_icon.py +++ b/src/app_model/types/_icon.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Generator, Optional, TypedDict, Union +from collections.abc import Generator +from typing import Any, Callable, Optional, TypedDict, Union from pydantic_compat import Field, model_validator diff --git a/src/app_model/types/_keybinding_rule.py b/src/app_model/types/_keybinding_rule.py index 727e8ac9..e97bf4dc 100644 --- a/src/app_model/types/_keybinding_rule.py +++ b/src/app_model/types/_keybinding_rule.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Optional, Type, TypedDict, TypeVar, Union +from typing import Any, Callable, Optional, TypedDict, TypeVar, Union from pydantic_compat import PYDANTIC2, Field, model_validator @@ -69,7 +69,7 @@ def _bind_to_current_platform(self) -> Optional[KeyEncoding]: @model_validator(mode="wrap") @classmethod def _model_val( - cls: Type[M], v: Any, handler: Callable[[Any], M] + cls: type[M], v: Any, handler: Callable[[Any], M] ) -> "KeyBindingRule": if isinstance(v, StandardKeyBinding): return v.to_keybinding_rule() diff --git a/src/app_model/types/_keys/_keybindings.py b/src/app_model/types/_keys/_keybindings.py index 60f27fd9..c958046c 100644 --- a/src/app_model/types/_keys/_keybindings.py +++ b/src/app_model/types/_keys/_keybindings.py @@ -1,5 +1,6 @@ import re -from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, List, Optional, Tuple +from collections.abc import Generator +from typing import TYPE_CHECKING, Any, Callable, Optional from pydantic_compat import PYDANTIC2, BaseModel, Field, model_validator @@ -111,7 +112,7 @@ def to_int(self, os: Optional[OperatingSystem] = None) -> int: mods |= KeyMod.CtrlCmd if os.is_mac else KeyMod.WinCtrl return mods | (self.key or 0) - def _mods2keycodes(self) -> List[KeyCode]: + def _mods2keycodes(self) -> list[KeyCode]: """Create KeyCode instances list of modifiers from this SimpleKeyBinding.""" mods = [] if self.ctrl: @@ -185,9 +186,9 @@ class KeyBinding: The parts of the keybinding. There must be at least one part. """ - parts: List[SimpleKeyBinding] = Field(..., **MIN1) # type: ignore + parts: list[SimpleKeyBinding] = Field(..., **MIN1) # type: ignore - def __init__(self, *, parts: List[SimpleKeyBinding]): + def __init__(self, *, parts: list[SimpleKeyBinding]): self.parts = parts def __str__(self) -> str: @@ -310,7 +311,7 @@ def validate(cls, v: Any) -> "KeyBinding": _re_meta = re.compile(r"(meta|super|win|windows|⊞|cmd|command|⌘)[\+|\-]") -def _parse_modifiers(input: str) -> Tuple[Dict[str, bool], str]: +def _parse_modifiers(input: str) -> tuple[dict[str, bool], str]: """Parse modifiers from a string (case insensitive). modifiers must start at the beginning of the string, and be separated by diff --git a/src/app_model/types/_menu_rule.py b/src/app_model/types/_menu_rule.py index 500b8df6..535deb49 100644 --- a/src/app_model/types/_menu_rule.py +++ b/src/app_model/types/_menu_rule.py @@ -1,9 +1,8 @@ +from collections.abc import Generator from typing import ( Any, Callable, - Generator, Optional, - Type, TypedDict, Union, ) @@ -43,7 +42,7 @@ def __get_validators__(cls) -> Generator[Callable[..., Any], None, None]: yield cls._validate @classmethod - def _validate(cls: Type["MenuItemBase"], v: Any) -> "MenuItemBase": + def _validate(cls: type["MenuItemBase"], v: Any) -> "MenuItemBase": """Validate icon.""" if isinstance(v, MenuItemBase): return v @@ -67,7 +66,7 @@ class MenuRule(MenuItemBase): # for v1 @classmethod - def _validate(cls: Type["MenuRule"], v: Any) -> Any: + def _validate(cls: type["MenuRule"], v: Any) -> Any: if isinstance(v, str): v = {"id": v} return super()._validate(v) diff --git a/tests/conftest.py b/tests/conftest.py index b165cdb1..5cae8797 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,7 +11,8 @@ from app_model.types import KeyCode, KeyMod, SubmenuItem if TYPE_CHECKING: - from typing import Iterator, NoReturn + from collections.abc import Iterator + from typing import NoReturn try: from fonticon_fa6 import FA6S diff --git a/tests/test_actions.py b/tests/test_actions.py index e56d5261..356136d6 100644 --- a/tests/test_actions.py +++ b/tests/test_actions.py @@ -1,5 +1,3 @@ -from typing import List - import pytest from app_model import Application @@ -109,7 +107,7 @@ def test_errors(simple_app: Application): def test_register_multiple_actions(simple_app: Application): - actions: List[Action] = [ + actions: list[Action] = [ Action(id="cmd_id1", title="title1", callback=lambda: None), Action(id="cmd_id2", title="title2", callback=lambda: None), ]