Skip to content

Commit 250f703

Browse files
authored
Drop pydantic1 (#392)
1 parent 322eff6 commit 250f703

File tree

5 files changed

+120
-472
lines changed

5 files changed

+120
-472
lines changed

.github/workflows/test.yml

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
- run: pipx run check-manifest
2121

2222
test:
23-
name: Test ${{ matrix.qt }} ${{ matrix.os }} py${{ matrix.python-version }} mypyc-${{ matrix.compile }} ${{ matrix.pydantic }}
23+
name: Test ${{ matrix.qt }} ${{ matrix.os }} py${{ matrix.python-version }} mypyc-${{ matrix.compile }}
2424
runs-on: ${{ matrix.os }}
2525
env:
2626
HATCH_BUILD_HOOKS_ENABLE: ${{ matrix.compile }}
@@ -32,23 +32,14 @@ jobs:
3232
os: [ubuntu-latest, macos-latest, windows-latest]
3333
compile: ["1", "0"]
3434
qt: [""]
35-
pydantic: [""]
3635
include:
3736
- os: ubuntu-latest
3837
python-version: "3.11"
3938
compile: "1"
4039
- os: ubuntu-latest
4140
python-version: "3.13"
4241
compile: "1"
43-
# pydantic versions
44-
- os: ubuntu-latest
45-
python-version: "3.11"
46-
pydantic: "pydantic==1.10"
47-
compile: "1"
48-
- os: ubuntu-latest
49-
python-version: "3.10"
50-
pydantic: "pydantic==1.10"
51-
compile: "0"
42+
5243
# qt stuff
5344
- os: macos-latest
5445
python-version: "3.11"
@@ -86,18 +77,14 @@ jobs:
8677
uv sync --no-dev --group testqt
8778
uv pip install ${{ matrix.qt }}
8879
89-
- if: matrix.pydantic != ''
90-
name: downgrade pydantic
91-
run: uv pip install ${{ matrix.pydantic }}
92-
9380
- name: Test
9481
shell: bash
9582
run: uv run coverage run -p -m pytest -v
9683

9784
- name: Upload coverage
9885
uses: actions/upload-artifact@v4
9986
with:
100-
name: covreport-${{ matrix.os }}-py${{ matrix.python-version }}-mypyc${{ matrix.compile }}-${{ matrix.qt }}-${{ matrix.pydantic }}
87+
name: covreport-${{ matrix.os }}-py${{ matrix.python-version }}-mypyc${{ matrix.compile }}-${{ matrix.qt }}
10188
path: ./.coverage*
10289
include-hidden-files: true
10390

src/psygnal/_evented_model.py

Lines changed: 41 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
TYPE_CHECKING,
77
Any,
88
ClassVar,
9-
NamedTuple,
109
Union,
1110
cast,
1211
no_type_check,
@@ -19,13 +18,10 @@
1918
from ._group_descriptor import _check_field_equality, _pick_equality_operator
2019
from ._signal import ReemissionMode, Signal
2120

22-
PYDANTIC_V1 = pydantic.version.VERSION.startswith("1")
23-
2421
if TYPE_CHECKING:
2522
from inspect import Signature
2623
from typing import TypeGuard
2724

28-
from pydantic import ConfigDict
2925
from pydantic._internal import _model_construction as pydantic_main
3026
from pydantic._internal import _utils as utils
3127
from pydantic._internal._decorators import PydanticDescriptorProxy
@@ -35,12 +31,8 @@
3531

3632
EqOperator = Callable[[Any, Any], bool]
3733
else:
38-
if PYDANTIC_V1:
39-
import pydantic.main as pydantic_main
40-
from pydantic import utils
41-
else:
42-
from pydantic._internal import _model_construction as pydantic_main
43-
from pydantic._internal import _utils as utils
34+
from pydantic._internal import _model_construction as pydantic_main
35+
from pydantic._internal import _utils as utils
4436

4537
try:
4638
# py311
@@ -103,89 +95,42 @@ def _return2(x: str, y: "Signature") -> "Signature":
10395
pydantic_main.ClassAttribute = utils.ClassAttribute # type: ignore
10496

10597

106-
if not PYDANTIC_V1:
107-
108-
def _get_defaults(
109-
obj: pydantic.BaseModel | type[pydantic.BaseModel],
110-
) -> dict[str, Any]:
111-
"""Get possibly nested default values for a Model object."""
112-
dflt = {}
113-
cls = obj if isinstance(obj, type) else type(obj)
114-
for k, v in cls.model_fields.items():
115-
d = v.get_default()
116-
if (
117-
d is None
118-
and isinstance(v.annotation, type)
119-
and issubclass(v.annotation, pydantic.BaseModel)
120-
):
121-
d = _get_defaults(v.annotation) # pragma: no cover
122-
dflt[k] = d
123-
return dflt
124-
125-
def _get_config(cls: pydantic.BaseModel) -> "ConfigDict":
126-
return cls.model_config
127-
128-
def _get_fields(
129-
cls: type[pydantic.BaseModel],
130-
) -> dict[str, pydantic.fields.FieldInfo]:
131-
comp_fields = {
132-
name: pydantic.fields.FieldInfo(annotation=f.return_type, frozen=False)
133-
for name, f in cls.model_computed_fields.items()
134-
}
135-
return {**cls.model_fields, **comp_fields}
136-
137-
def _model_dump(obj: pydantic.BaseModel) -> dict:
138-
return obj.model_dump()
139-
140-
def _is_pydantic_descriptor_proxy(obj: Any) -> "TypeGuard[PydanticDescriptorProxy]":
98+
def _get_defaults(
99+
obj: pydantic.BaseModel | type[pydantic.BaseModel],
100+
) -> dict[str, Any]:
101+
"""Get possibly nested default values for a Model object."""
102+
dflt = {}
103+
cls = obj if isinstance(obj, type) else type(obj)
104+
for k, v in cls.model_fields.items():
105+
d = v.get_default()
141106
if (
142-
type(obj).__module__.startswith("pydantic")
143-
and type(obj).__name__ == "PydanticDescriptorProxy"
144-
and isinstance(getattr(obj, "wrapped", None), property)
107+
d is None
108+
and isinstance(v.annotation, type)
109+
and issubclass(v.annotation, pydantic.BaseModel)
145110
):
146-
return True
147-
return False
148-
149-
else:
150-
151-
@no_type_check
152-
def _get_defaults(obj: pydantic.BaseModel) -> dict[str, Any]:
153-
"""Get possibly nested default values for a Model object."""
154-
dflt = {}
155-
for k, v in obj.__fields__.items():
156-
d = v.get_default()
157-
if d is None and isinstance(v.type_, pydantic_main.ModelMetaclass):
158-
d = _get_defaults(v.type_) # pragma: no cover
159-
dflt[k] = d
160-
return dflt
161-
162-
class GetAttrAsItem:
163-
def __init__(self, obj: Any) -> None:
164-
self._obj = obj
165-
166-
def get(self, key: str, default: Any = None) -> Any:
167-
return getattr(self._obj, key, default)
168-
169-
@no_type_check
170-
def _get_config(cls: type) -> "ConfigDict":
171-
return GetAttrAsItem(cls.__config__)
172-
173-
class FieldInfo(NamedTuple):
174-
annotation: type[Any] | None
175-
frozen: bool | None
176-
177-
@no_type_check
178-
def _get_fields(cls: type) -> dict[str, FieldInfo]:
179-
return {
180-
k: FieldInfo(annotation=f.type_, frozen=not f.field_info.allow_mutation)
181-
for k, f in cls.__fields__.items()
182-
}
183-
184-
def _model_dump(obj: pydantic.BaseModel) -> dict:
185-
return obj.dict()
186-
187-
def _is_pydantic_descriptor_proxy(obj: Any) -> "TypeGuard[PydanticDescriptorProxy]":
188-
return False
111+
d = _get_defaults(v.annotation) # pragma: no cover
112+
dflt[k] = d
113+
return dflt
114+
115+
116+
def _get_fields(
117+
cls: type[pydantic.BaseModel],
118+
) -> dict[str, pydantic.fields.FieldInfo]:
119+
comp_fields = {
120+
name: pydantic.fields.FieldInfo(annotation=f.return_type, frozen=False)
121+
for name, f in cls.model_computed_fields.items()
122+
}
123+
return {**cls.model_fields, **comp_fields}
124+
125+
126+
def _is_pydantic_descriptor_proxy(obj: Any) -> "TypeGuard[PydanticDescriptorProxy]":
127+
if (
128+
type(obj).__module__.startswith("pydantic")
129+
and type(obj).__name__ == "PydanticDescriptorProxy"
130+
and isinstance(getattr(obj, "wrapped", None), property)
131+
):
132+
return True
133+
return False
189134

190135

191136
class ComparisonDelayer:
@@ -230,7 +175,7 @@ def __new__(
230175
signals = {}
231176

232177
model_fields = _get_fields(cls)
233-
model_config = _get_config(cls)
178+
model_config = cls.model_config
234179

235180
emission_cfg = model_config.get(REEMISSION, {})
236181
default_strategy: ReemissionMode = ReemissionMode.LATEST
@@ -255,20 +200,6 @@ def __new__(
255200
recursion = emission_map.get(n, default_strategy)
256201
signals[n] = Signal(f.annotation, reemission=recursion)
257202

258-
# If a field type has a _json_encode method, add it to the json
259-
# encoders for this model.
260-
# NOTE: a _json_encode field must return an object that can be
261-
# passed to json.dumps ... but it needn't return a string.
262-
if PYDANTIC_V1 and hasattr(f.annotation, "_json_encode"):
263-
encoder = f.annotation._json_encode
264-
cls.__config__.json_encoders[f.annotation] = encoder
265-
# also add it to the base config
266-
# required for pydantic>=1.8.0 due to:
267-
# https://github.com/samuelcolvin/pydantic/pull/2064
268-
for base in cls.__bases__:
269-
if hasattr(base, "__config__"):
270-
base.__config__.json_encoders[f.annotation] = encoder
271-
272203
allow_props = model_config.get(ALLOW_PROPERTY_SETTERS, False)
273204

274205
# check for @_.setters defined on the class, so we can allow them
@@ -290,7 +221,7 @@ def __new__(
290221
else:
291222
for b in cls.__bases__:
292223
with suppress(AttributeError):
293-
conf = _get_config(b)
224+
conf = b.model_config
294225
if conf and conf.get(ALLOW_PROPERTY_SETTERS, False):
295226
raise ValueError(
296227
"Cannot set 'allow_property_setters' to 'False' when base "
@@ -337,15 +268,6 @@ class Config:
337268
deps: dict[str, set[str]] = {}
338269

339270
cfg_deps = model_config.get(FIELD_DEPENDENCIES, {}) # sourcery skip
340-
if not cfg_deps:
341-
cfg_deps = model_config.get("property_dependencies", {})
342-
if cfg_deps:
343-
warnings.warn(
344-
"The 'property_dependencies' configuration key is deprecated. "
345-
"Use 'field_dependencies' instead",
346-
DeprecationWarning,
347-
stacklevel=2,
348-
)
349271

350272
if cfg_deps:
351273
if not isinstance(cfg_deps, dict): # pragma: no cover
@@ -481,12 +403,6 @@ class Config:
481403
_primary_changes: set[str] = PrivateAttr(default_factory=set)
482404
_delay_check_semaphore: int = PrivateAttr(0)
483405

484-
if PYDANTIC_V1:
485-
486-
class Config:
487-
# this seems to be necessary for the _json_encoders trick to work
488-
json_encoders: ClassVar[dict] = {"____": None}
489-
490406
def __init__(_model_self_, **data: Any) -> None:
491407
super().__init__(**data)
492408
Group = _model_self_.__signal_group__
@@ -514,7 +430,7 @@ def __eq__(self, other: Any) -> bool:
514430
is constructed in ``EqualityMetaclass.__new__``
515431
"""
516432
if not isinstance(other, EventedModel):
517-
return bool(_model_dump(self) == other)
433+
return bool(self.model_dump() == other)
518434

519435
for f_name, _ in self.__eq_operators__.items():
520436
if not hasattr(self, f_name) or not hasattr(other, f_name):
@@ -541,7 +457,7 @@ def update(self, values: Union["EventedModel", dict], recurse: bool = True) -> N
541457
different realized types with different fields.
542458
"""
543459
if isinstance(values, pydantic.BaseModel):
544-
values = _model_dump(values)
460+
values = values.model_dump()
545461

546462
if not isinstance(values, dict): # pragma: no cover
547463
raise TypeError(f"values must be a dict or BaseModel. got {type(values)}")
@@ -556,7 +472,7 @@ def update(self, values: Union["EventedModel", dict], recurse: bool = True) -> N
556472

557473
def reset(self) -> None:
558474
"""Reset the state of the model to default values."""
559-
model_config = _get_config(self)
475+
model_config = self.model_config
560476
model_fields = _get_fields(type(self))
561477
for name, value in self._defaults.items():
562478
if isinstance(value, EventedModel):
@@ -731,50 +647,3 @@ def _setattr_with_dependents_impl(self, name: str, value: Any) -> None:
731647
if dep not in self._changes_queue:
732648
self._changes_queue[dep] = getattr(self, dep, object())
733649
self._super_setattr_(name, value)
734-
735-
if PYDANTIC_V1:
736-
737-
@contextmanager
738-
def enums_as_values(self, as_values: bool = True) -> Iterator[None]:
739-
"""Temporarily override how enums are retrieved.
740-
741-
Parameters
742-
----------
743-
as_values : bool
744-
Whether enums should be shown as values (or as enum objects),
745-
by default `True`
746-
"""
747-
before = getattr(self.Config, "use_enum_values", NULL)
748-
self.Config.use_enum_values = as_values # type: ignore
749-
try:
750-
yield
751-
finally:
752-
if before is not NULL:
753-
self.Config.use_enum_values = before # type: ignore # pragma: no cover
754-
else:
755-
delattr(self.Config, "use_enum_values")
756-
757-
else:
758-
759-
@classmethod
760-
@contextmanager
761-
def enums_as_values( # type: ignore [misc] # Incompatible redefinition
762-
cls, as_values: bool = True
763-
) -> Iterator[None]: # pragma: no cover
764-
"""Temporarily override how enums are retrieved.
765-
766-
Parameters
767-
----------
768-
as_values : bool
769-
Whether enums should be shown as values (or as enum objects),
770-
by default `True`
771-
"""
772-
before = cls.model_config.get("use_enum_values", NULL)
773-
cls.model_config["use_enum_values"] = as_values
774-
try:
775-
yield
776-
finally:
777-
if before is not NULL: # pragma: no cover
778-
cls.model_config["use_enum_values"] = cast("bool", before)
779-
else:
780-
cls.model_config.pop("use_enum_values")

0 commit comments

Comments
 (0)