From af6954c603c0b6d78aed7305674d7d03348936cf Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Tue, 2 Sep 2025 10:33:08 -0400 Subject: [PATCH 1/3] drop pydantic 1 --- pyproject.toml | 3 +- src/app_model/types/_action.py | 2 +- src/app_model/types/_base.py | 2 +- src/app_model/types/_command_rule.py | 2 +- src/app_model/types/_icon.py | 2 +- src/app_model/types/_keybinding_rule.py | 34 ++++++----------------- src/app_model/types/_keys/_keybindings.py | 4 +-- src/app_model/types/_menu_rule.py | 11 ++------ tests/test_keybindings.py | 11 ++------ 9 files changed, 20 insertions(+), 51 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c231d004..a91a7f71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,8 +38,7 @@ classifiers = [ dynamic = ["version"] dependencies = [ "psygnal>=0.10", - "pydantic>=1.10.18", - "pydantic-compat>=0.1.1", + "pydantic>=2.10", "in-n-out>=0.1.5", "typing_extensions>=4.12", ] diff --git a/src/app_model/types/_action.py b/src/app_model/types/_action.py index 669e099e..f39a742f 100644 --- a/src/app_model/types/_action.py +++ b/src/app_model/types/_action.py @@ -1,6 +1,6 @@ from typing import TYPE_CHECKING, Callable, Generic, Optional, TypeVar, Union -from pydantic_compat import Field, field_validator +from pydantic import Field, field_validator from ._command_rule import CommandRule from ._keybinding_rule import KeyBindingRule diff --git a/src/app_model/types/_base.py b/src/app_model/types/_base.py index e17aec80..2776ef59 100644 --- a/src/app_model/types/_base.py +++ b/src/app_model/types/_base.py @@ -1,6 +1,6 @@ from typing import TYPE_CHECKING, ClassVar -from pydantic_compat import BaseModel +from pydantic import BaseModel if TYPE_CHECKING: from pydantic import ConfigDict diff --git a/src/app_model/types/_command_rule.py b/src/app_model/types/_command_rule.py index df06d3ed..115c6cc0 100644 --- a/src/app_model/types/_command_rule.py +++ b/src/app_model/types/_command_rule.py @@ -1,6 +1,6 @@ from typing import Callable, Optional, Union -from pydantic_compat import Field +from pydantic import Field from app_model import expressions diff --git a/src/app_model/types/_icon.py b/src/app_model/types/_icon.py index a416728f..5d6d80cd 100644 --- a/src/app_model/types/_icon.py +++ b/src/app_model/types/_icon.py @@ -1,7 +1,7 @@ from collections.abc import Generator from typing import Any, Callable, Optional, TypedDict, Union -from pydantic_compat import Field, model_validator +from pydantic import Field, model_validator from ._base import _BaseModel diff --git a/src/app_model/types/_keybinding_rule.py b/src/app_model/types/_keybinding_rule.py index f19d3de3..adb99a83 100644 --- a/src/app_model/types/_keybinding_rule.py +++ b/src/app_model/types/_keybinding_rule.py @@ -1,6 +1,6 @@ from typing import Any, Callable, Optional, TypedDict, TypeVar, Union -from pydantic_compat import PYDANTIC2, Field, model_validator +from pydantic import Field, model_validator from app_model import expressions @@ -63,30 +63,14 @@ def _bind_to_current_platform(self) -> Optional[KeyEncoding]: return self.linux return self.primary - # These methods are here to make KeyBindingRule work as a field - # there are better ways to do this now with pydantic v2... but it still - # feels a bit in flux. pydantic_compat might not yet work for this (or - # at least in my playing around i couldn't get it) - # so sticking with this one conditional method here... - if PYDANTIC2: - # for v2 - @model_validator(mode="wrap") - @classmethod - def _model_val( - cls: type[M], v: Any, handler: Callable[[Any], M] - ) -> "KeyBindingRule": - if isinstance(v, StandardKeyBinding): - return v.to_keybinding_rule() - return handler(v) # type: ignore - - else: - - @classmethod - def validate(cls, value: Any) -> "KeyBindingRule": - """Validate keybinding rule.""" - if isinstance(value, StandardKeyBinding): - return value.to_keybinding_rule() - return super().validate(value) + @model_validator(mode="wrap") + @classmethod + def _model_val( + cls: type[M], v: Any, handler: Callable[[Any], M] + ) -> "KeyBindingRule": + if isinstance(v, StandardKeyBinding): + return v.to_keybinding_rule() + return handler(v) # type: ignore class KeyBindingRuleDict(TypedDict, total=False): diff --git a/src/app_model/types/_keys/_keybindings.py b/src/app_model/types/_keys/_keybindings.py index bcd6fb3c..18e216e8 100644 --- a/src/app_model/types/_keys/_keybindings.py +++ b/src/app_model/types/_keys/_keybindings.py @@ -2,7 +2,7 @@ from collections.abc import Generator from typing import TYPE_CHECKING, Any, Callable, Optional -from pydantic_compat import PYDANTIC2, BaseModel, Field, model_validator +from pydantic import BaseModel, Field, model_validator from app_model.types._constants import OperatingSystem @@ -166,7 +166,7 @@ def _model_val(cls, instance: "SimpleKeyBinding") -> "SimpleKeyBinding": return cls._parse_input(instance) -MIN1 = {"min_length": 1} if PYDANTIC2 else {"min_items": 1} +MIN1 = {"min_length": 1} class KeyBinding: diff --git a/src/app_model/types/_menu_rule.py b/src/app_model/types/_menu_rule.py index 5ec9f88c..6bc9f7e0 100644 --- a/src/app_model/types/_menu_rule.py +++ b/src/app_model/types/_menu_rule.py @@ -7,7 +7,7 @@ Union, ) -from pydantic_compat import Field, field_validator, model_validator +from pydantic import Field, field_validator, model_validator from app_model import expressions @@ -64,15 +64,8 @@ class MenuRule(MenuItemBase): id: str = Field(..., description="Menu in which to place this item.") - # for v1 - @classmethod - def _validate(cls: type["MenuRule"], v: Any) -> Any: - if isinstance(v, str): - v = {"id": v} - return super()._validate(v) - - # for v2 @model_validator(mode="before") + @classmethod def _validate_model(cls, v: Any) -> Any: """If a single string is provided, convert to a dict with `id` key.""" return {"id": v} if isinstance(v, str) else v diff --git a/tests/test_keybindings.py b/tests/test_keybindings.py index 1223db69..503f8187 100644 --- a/tests/test_keybindings.py +++ b/tests/test_keybindings.py @@ -1,9 +1,8 @@ import itertools import sys -from typing import ClassVar import pytest -from pydantic_compat import PYDANTIC2, BaseModel +from pydantic import BaseModel from app_model.types import ( KeyBinding, @@ -179,14 +178,8 @@ def test_in_model() -> None: class M(BaseModel): key: KeyBinding - if not PYDANTIC2: - - class Config: - json_encoders: ClassVar[dict] = {KeyBinding: str} - m = M(key="Shift+A B") - # pydantic v1 and v2 have slightly different json outputs - assert m.model_dump_json().replace('": "', '":"') == '{"key":"Shift+A B"}' + assert m.model_dump_json() == '{"key":"Shift+A B"}' def test_standard_keybindings() -> None: From f73ef959f824fd67b6f9d4c793dfe8c003189d7b Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Tue, 2 Sep 2025 10:43:41 -0400 Subject: [PATCH 2/3] relax pydantic pin --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a91a7f71..8ed91f05 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ classifiers = [ dynamic = ["version"] dependencies = [ "psygnal>=0.10", - "pydantic>=2.10", + "pydantic>=2.8", "in-n-out>=0.1.5", "typing_extensions>=4.12", ] @@ -52,7 +52,7 @@ qt = ["qtpy>=2.4.0", "superqt[iconify]>=0.7.2"] pyqt5 = [ "app-model[qt]", "PyQt5>=5.15.10", - "pyqt5-qt5<=5.15.2; sys_platform == 'win32'", + "pyqt5-qt5==5.15.2; sys_platform == 'win32'", "pyqt5-qt5>=5.15.4; sys_platform != 'win32'", ] pyqt6 = ["app-model[qt]", "PyQt6>=6.4.0"] From 52173d096c324a25296c8f2974d73da867a2828c Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Tue, 2 Sep 2025 10:47:57 -0400 Subject: [PATCH 3/3] remove more --- .pre-commit-config.yaml | 3 +-- pyproject.toml | 2 -- src/app_model/expressions/_expressions.py | 6 ------ src/app_model/types/_icon.py | 7 +------ src/app_model/types/_keys/_key_codes.py | 3 --- src/app_model/types/_keys/_keybindings.py | 7 +------ src/app_model/types/_menu_rule.py | 6 ------ 7 files changed, 3 insertions(+), 31 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e66d6968..12e51fe8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,8 +28,7 @@ repos: - id: mypy files: "^src/" additional_dependencies: - - pydantic >2 - - pydantic-compat + - pydantic >2.8 - in-n-out - repo: local diff --git a/pyproject.toml b/pyproject.toml index 8ed91f05..d446e00a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -153,8 +153,6 @@ filterwarnings = [ "error", "ignore:Enum value:DeprecationWarning:superqt", "ignore:Failed to disconnect::pytestqt", - "ignore:Failing to pass a value to the 'type_params' parameter::pydantic", - "ignore:`__get_validators__` is deprecated and will be removed", ] # https://mypy.readthedocs.io/en/stable/config_file.html diff --git a/src/app_model/expressions/_expressions.py b/src/app_model/expressions/_expressions.py index a285659d..2e7b180d 100644 --- a/src/app_model/expressions/_expressions.py +++ b/src/app_model/expressions/_expressions.py @@ -6,7 +6,6 @@ from typing import ( TYPE_CHECKING, Any, - Callable, Generic, SupportsIndex, TypeVar, @@ -342,11 +341,6 @@ def __reduce_ex__(self, protocol: SupportsIndex) -> tuple[Any, ...]: rv[1] = tuple(getattr(self, f) for f in self._fields) return tuple(rv) - @classmethod - def __get_validators__(cls) -> Iterator[Callable[[Any], Expr]]: - """Pydantic validators for this class.""" - yield cls._validate - @classmethod def __get_pydantic_core_schema__( cls, source: type, handler: GetCoreSchemaHandler diff --git a/src/app_model/types/_icon.py b/src/app_model/types/_icon.py index 5d6d80cd..a0764884 100644 --- a/src/app_model/types/_icon.py +++ b/src/app_model/types/_icon.py @@ -1,5 +1,4 @@ -from collections.abc import Generator -from typing import Any, Callable, Optional, TypedDict, Union +from typing import Any, Optional, TypedDict, Union from pydantic import Field, model_validator @@ -30,10 +29,6 @@ class Icon(_BaseModel): " keys, such as `fa6s.arrow_down`", ) - @classmethod - def __get_validators__(cls) -> Generator[Callable[..., Any], None, None]: - yield cls._validate - @classmethod def _validate(cls, v: Any) -> "Icon": """Validate icon.""" diff --git a/src/app_model/types/_keys/_key_codes.py b/src/app_model/types/_keys/_key_codes.py index 7e6c3cf6..8594efcf 100644 --- a/src/app_model/types/_keys/_key_codes.py +++ b/src/app_model/types/_keys/_key_codes.py @@ -199,9 +199,6 @@ def from_event_code(cls, event_code: int) -> 'KeyCode': """ return _EVENTCODE_TO_KEYCODE.get(event_code, KeyCode.UNKNOWN) - @classmethod - def __get_validators__(cls) -> Generator[Callable[..., 'KeyCode'], None, None]: - yield cls.validate @classmethod def __get_pydantic_core_schema__( diff --git a/src/app_model/types/_keys/_keybindings.py b/src/app_model/types/_keys/_keybindings.py index 18e216e8..ee8f5dcd 100644 --- a/src/app_model/types/_keys/_keybindings.py +++ b/src/app_model/types/_keys/_keybindings.py @@ -1,6 +1,5 @@ import re -from collections.abc import Generator -from typing import TYPE_CHECKING, Any, Callable, Optional +from typing import TYPE_CHECKING, Any, Optional from pydantic import BaseModel, Field, model_validator @@ -277,10 +276,6 @@ def __int__(self) -> int: def __hash__(self) -> int: return hash(tuple(self.parts)) - @classmethod - def __get_validators__(cls) -> Generator[Callable[..., Any], None, None]: - yield cls.validate # pragma: no cover - @classmethod def __get_pydantic_core_schema__( cls, source: type, handler: "GetCoreSchemaHandler" diff --git a/src/app_model/types/_menu_rule.py b/src/app_model/types/_menu_rule.py index 6bc9f7e0..ee37cb29 100644 --- a/src/app_model/types/_menu_rule.py +++ b/src/app_model/types/_menu_rule.py @@ -1,7 +1,5 @@ -from collections.abc import Generator from typing import ( Any, - Callable, Optional, TypedDict, Union, @@ -37,10 +35,6 @@ class MenuItemBase(_BaseModel): "and the syntax 'group@order'. If not provided, items are sorted by title.", ) - @classmethod - def __get_validators__(cls) -> Generator[Callable[..., Any], None, None]: - yield cls._validate - @classmethod def _validate(cls: type["MenuItemBase"], v: Any) -> "MenuItemBase": """Validate icon."""