Skip to content

Commit 55b2b83

Browse files
feat: improve future compat with pydantic v3
1 parent d486aa9 commit 55b2b83

File tree

15 files changed

+438
-138
lines changed

15 files changed

+438
-138
lines changed

src/imagekit/_base_client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
ModelBuilderProtocol,
6060
)
6161
from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping
62-
from ._compat import PYDANTIC_V2, model_copy, model_dump
62+
from ._compat import PYDANTIC_V1, model_copy, model_dump
6363
from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type
6464
from ._response import (
6565
APIResponse,
@@ -232,7 +232,7 @@ def _set_private_attributes(
232232
model: Type[_T],
233233
options: FinalRequestOptions,
234234
) -> None:
235-
if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None:
235+
if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None:
236236
self.__pydantic_private__ = {}
237237

238238
self._model = model
@@ -320,7 +320,7 @@ def _set_private_attributes(
320320
client: AsyncAPIClient,
321321
options: FinalRequestOptions,
322322
) -> None:
323-
if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None:
323+
if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None:
324324
self.__pydantic_private__ = {}
325325

326326
self._model = model

src/imagekit/_compat.py

Lines changed: 48 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@
1212
_T = TypeVar("_T")
1313
_ModelT = TypeVar("_ModelT", bound=pydantic.BaseModel)
1414

15-
# --------------- Pydantic v2 compatibility ---------------
15+
# --------------- Pydantic v2, v3 compatibility ---------------
1616

1717
# Pyright incorrectly reports some of our functions as overriding a method when they don't
1818
# pyright: reportIncompatibleMethodOverride=false
1919

20-
PYDANTIC_V2 = pydantic.VERSION.startswith("2.")
20+
PYDANTIC_V1 = pydantic.VERSION.startswith("1.")
2121

22-
# v1 re-exports
2322
if TYPE_CHECKING:
2423

2524
def parse_date(value: date | StrBytesIntFloat) -> date: # noqa: ARG001
@@ -44,90 +43,92 @@ def is_typeddict(type_: type[Any]) -> bool: # noqa: ARG001
4443
...
4544

4645
else:
47-
if PYDANTIC_V2:
48-
from pydantic.v1.typing import (
46+
# v1 re-exports
47+
if PYDANTIC_V1:
48+
from pydantic.typing import (
4949
get_args as get_args,
5050
is_union as is_union,
5151
get_origin as get_origin,
5252
is_typeddict as is_typeddict,
5353
is_literal_type as is_literal_type,
5454
)
55-
from pydantic.v1.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime
55+
from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime
5656
else:
57-
from pydantic.typing import (
57+
from ._utils import (
5858
get_args as get_args,
5959
is_union as is_union,
6060
get_origin as get_origin,
61+
parse_date as parse_date,
6162
is_typeddict as is_typeddict,
63+
parse_datetime as parse_datetime,
6264
is_literal_type as is_literal_type,
6365
)
64-
from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime
6566

6667

6768
# refactored config
6869
if TYPE_CHECKING:
6970
from pydantic import ConfigDict as ConfigDict
7071
else:
71-
if PYDANTIC_V2:
72-
from pydantic import ConfigDict
73-
else:
72+
if PYDANTIC_V1:
7473
# TODO: provide an error message here?
7574
ConfigDict = None
75+
else:
76+
from pydantic import ConfigDict as ConfigDict
7677

7778

7879
# renamed methods / properties
7980
def parse_obj(model: type[_ModelT], value: object) -> _ModelT:
80-
if PYDANTIC_V2:
81-
return model.model_validate(value)
82-
else:
81+
if PYDANTIC_V1:
8382
return cast(_ModelT, model.parse_obj(value)) # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
83+
else:
84+
return model.model_validate(value)
8485

8586

8687
def field_is_required(field: FieldInfo) -> bool:
87-
if PYDANTIC_V2:
88-
return field.is_required()
89-
return field.required # type: ignore
88+
if PYDANTIC_V1:
89+
return field.required # type: ignore
90+
return field.is_required()
9091

9192

9293
def field_get_default(field: FieldInfo) -> Any:
9394
value = field.get_default()
94-
if PYDANTIC_V2:
95-
from pydantic_core import PydanticUndefined
96-
97-
if value == PydanticUndefined:
98-
return None
95+
if PYDANTIC_V1:
9996
return value
97+
from pydantic_core import PydanticUndefined
98+
99+
if value == PydanticUndefined:
100+
return None
100101
return value
101102

102103

103104
def field_outer_type(field: FieldInfo) -> Any:
104-
if PYDANTIC_V2:
105-
return field.annotation
106-
return field.outer_type_ # type: ignore
105+
if PYDANTIC_V1:
106+
return field.outer_type_ # type: ignore
107+
return field.annotation
107108

108109

109110
def get_model_config(model: type[pydantic.BaseModel]) -> Any:
110-
if PYDANTIC_V2:
111-
return model.model_config
112-
return model.__config__ # type: ignore
111+
if PYDANTIC_V1:
112+
return model.__config__ # type: ignore
113+
return model.model_config
113114

114115

115116
def get_model_fields(model: type[pydantic.BaseModel]) -> dict[str, FieldInfo]:
116-
if PYDANTIC_V2:
117-
return model.model_fields
118-
return model.__fields__ # type: ignore
117+
if PYDANTIC_V1:
118+
return model.__fields__ # type: ignore
119+
return model.model_fields
119120

120121

121122
def model_copy(model: _ModelT, *, deep: bool = False) -> _ModelT:
122-
if PYDANTIC_V2:
123-
return model.model_copy(deep=deep)
124-
return model.copy(deep=deep) # type: ignore
123+
if PYDANTIC_V1:
124+
return model.copy(deep=deep) # type: ignore
125+
return model.model_copy(deep=deep)
125126

126127

127128
def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str:
128-
if PYDANTIC_V2:
129-
return model.model_dump_json(indent=indent)
130-
return model.json(indent=indent) # type: ignore
129+
if PYDANTIC_V1:
130+
return model.json(indent=indent) # type: ignore
131+
return model.model_dump_json(indent=indent)
131132

132133

133134
def model_dump(
@@ -139,14 +140,14 @@ def model_dump(
139140
warnings: bool = True,
140141
mode: Literal["json", "python"] = "python",
141142
) -> dict[str, Any]:
142-
if PYDANTIC_V2 or hasattr(model, "model_dump"):
143+
if (not PYDANTIC_V1) or hasattr(model, "model_dump"):
143144
return model.model_dump(
144145
mode=mode,
145146
exclude=exclude,
146147
exclude_unset=exclude_unset,
147148
exclude_defaults=exclude_defaults,
148149
# warnings are not supported in Pydantic v1
149-
warnings=warnings if PYDANTIC_V2 else True,
150+
warnings=True if PYDANTIC_V1 else warnings,
150151
)
151152
return cast(
152153
"dict[str, Any]",
@@ -159,9 +160,9 @@ def model_dump(
159160

160161

161162
def model_parse(model: type[_ModelT], data: Any) -> _ModelT:
162-
if PYDANTIC_V2:
163-
return model.model_validate(data)
164-
return model.parse_obj(data) # pyright: ignore[reportDeprecated]
163+
if PYDANTIC_V1:
164+
return model.parse_obj(data) # pyright: ignore[reportDeprecated]
165+
return model.model_validate(data)
165166

166167

167168
# generic models
@@ -170,17 +171,16 @@ def model_parse(model: type[_ModelT], data: Any) -> _ModelT:
170171
class GenericModel(pydantic.BaseModel): ...
171172

172173
else:
173-
if PYDANTIC_V2:
174+
if PYDANTIC_V1:
175+
import pydantic.generics
176+
177+
class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ...
178+
else:
174179
# there no longer needs to be a distinction in v2 but
175180
# we still have to create our own subclass to avoid
176181
# inconsistent MRO ordering errors
177182
class GenericModel(pydantic.BaseModel): ...
178183

179-
else:
180-
import pydantic.generics
181-
182-
class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ...
183-
184184

185185
# cached properties
186186
if TYPE_CHECKING:

0 commit comments

Comments
 (0)