diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e66d696..12e51fe 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 c231d00..d446e00 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.8", "in-n-out>=0.1.5", "typing_extensions>=4.12", ] @@ -53,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"] @@ -154,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 a285659..2e7b180 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/_action.py b/src/app_model/types/_action.py index 669e099..f39a742 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 e17aec8..2776ef5 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 df06d3e..115c6cc 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 a416728..a076488 100644 --- a/src/app_model/types/_icon.py +++ b/src/app_model/types/_icon.py @@ -1,7 +1,6 @@ -from collections.abc import Generator -from typing import Any, Callable, Optional, TypedDict, Union +from typing import Any, Optional, TypedDict, Union -from pydantic_compat import Field, model_validator +from pydantic import Field, model_validator from ._base import _BaseModel @@ -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/_keybinding_rule.py b/src/app_model/types/_keybinding_rule.py index f19d3de..adb99a8 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/_key_codes.py b/src/app_model/types/_keys/_key_codes.py index 7e6c3cf..8594efc 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 bcd6fb3..ee8f5dc 100644 --- a/src/app_model/types/_keys/_keybindings.py +++ b/src/app_model/types/_keys/_keybindings.py @@ -1,8 +1,7 @@ import re -from collections.abc import Generator -from typing import TYPE_CHECKING, Any, Callable, Optional +from typing import TYPE_CHECKING, Any, 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 +165,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: @@ -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 5ec9f88..ee37cb2 100644 --- a/src/app_model/types/_menu_rule.py +++ b/src/app_model/types/_menu_rule.py @@ -1,13 +1,11 @@ -from collections.abc import Generator from typing import ( Any, - Callable, Optional, TypedDict, Union, ) -from pydantic_compat import Field, field_validator, model_validator +from pydantic import Field, field_validator, model_validator from app_model import expressions @@ -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.""" @@ -64,15 +58,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 1223db6..503f818 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: