Skip to content

Commit 0ad6d3b

Browse files
authored
Defer pydantic v1 import until it actually being used (#1676)
1 parent f6d5f83 commit 0ad6d3b

File tree

5 files changed

+54
-26
lines changed

5 files changed

+54
-26
lines changed

fastapi_pagination/customization.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,14 @@
6464
make_field_optional,
6565
make_field_required,
6666
)
67-
from .pydantic.v1 import BaseModelV1, ConfiguredBaseModelV1, FieldInfoV1, GenericModelV1, UndefinedV1, create_model_v1
6867
from .pydantic.v2 import FieldV2, UndefinedV2, is_pydantic_v2_model
6968
from .types import Cursor
7069
from .utils import get_caller
7170

71+
if TYPE_CHECKING:
72+
from .pydantic.v1 import BaseModelV1
73+
74+
7275
ClsNamespace: TypeAlias = dict[str, Any]
7376
PageCls: TypeAlias = "type[AbstractPage[Any]]"
7477

@@ -530,6 +533,11 @@ def model_post_init(_self: AbstractPage[Any], context: Any, /) -> None:
530533

531534

532535
def _convert_v2_field_to_v1(field_v2: FieldV2) -> tuple[Any, Any]:
536+
from .pydantic.v1 import (
537+
FieldInfoV1,
538+
UndefinedV1,
539+
)
540+
533541
default = field_v2.default
534542
if default is UndefinedV2:
535543
default = UndefinedV1
@@ -549,6 +557,13 @@ def _convert_v2_field_to_v1(field_v2: FieldV2) -> tuple[Any, Any]:
549557

550558

551559
def _convert_v2_page_cls_to_v1(page_cls: type[AbstractPage], /) -> BaseModelV1:
560+
from .pydantic.v1 import (
561+
BaseModelV1,
562+
ConfiguredBaseModelV1,
563+
GenericModelV1,
564+
create_model_v1,
565+
)
566+
552567
assert issubclass(page_cls, AbstractPage)
553568

554569
_create = page_cls.create.__func__ # type: ignore[attr-defined]

fastapi_pagination/pydantic/common.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
"make_field_required",
88
]
99

10+
import inspect
1011
from copy import copy
1112
from functools import singledispatch
1213
from typing import Annotated, Any, TypeVar, cast
1314

1415
from fastapi_pagination.typing_utils import remove_optional_from_tp
1516

17+
from .consts import IS_PYDANTIC_V2
1618
from .types import AnyBaseModel, AnyField
17-
from .v1 import FieldV1
1819
from .v2 import FieldV2, is_pydantic_v2_model
1920

2021
TAny = TypeVar("TAny")
@@ -37,16 +38,25 @@ def get_model_fields(model: type[AnyBaseModel], /) -> dict[str, AnyField]:
3738

3839

3940
def is_pydantic_field(field: Any, /) -> bool:
40-
return isinstance(field, (FieldV1, FieldV2))
41+
return is_pydantic_v2_field(field) or is_pydantic_v1_field(field)
4142

4243

43-
@singledispatch
44-
def make_field_optional(field: Any) -> Any: # pragma: no cover
45-
raise ValueError(f"Invalid field type {field!r}")
44+
def is_pydantic_v2_field(field: Any, /) -> bool:
45+
return isinstance(field, FieldV2)
4646

4747

48-
@make_field_optional.register
49-
def _(field: FieldV1, /) -> Any:
48+
def is_pydantic_v1_field(field: Any, /) -> bool:
49+
cls = type(field)
50+
51+
names = {"pydantic.v1.fields.ModelField"}
52+
if not IS_PYDANTIC_V2:
53+
names.add("pydantic.fields.ModelField")
54+
55+
return any(f"{mro_cls.__module__}.{mro_cls.__qualname__}" in names for mro_cls in inspect.getmro(cls))
56+
57+
58+
@singledispatch
59+
def make_field_optional(field: Any) -> Any: # pragma: no cover
5060
return None
5161

5262

@@ -63,11 +73,6 @@ def _(field: FieldV2, /) -> Any:
6373

6474
@singledispatch
6575
def make_field_required(field: Any, /) -> Any: # pragma: no cover
66-
raise ValueError(f"Invalid field type {field!r}")
67-
68-
69-
@make_field_required.register
70-
def _(field: FieldV1, /) -> Any:
7176
field = copy(field)
7277
field.required = True
7378
field.default = ...
@@ -89,11 +94,6 @@ def _(field: FieldV2, /) -> Any:
8994

9095
@singledispatch
9196
def get_field_tp(field: Any, /) -> Any: # pragma: no cover
92-
raise ValueError(f"Invalid field type {field!r}")
93-
94-
95-
@get_field_tp.register
96-
def _(field: FieldV1, /) -> Any:
9797
return field.type_
9898

9999

fastapi_pagination/pydantic/types.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,20 @@
55
"LatestGenericModel",
66
]
77

8-
from typing import TypeAlias
8+
from typing import TYPE_CHECKING, Any, TypeAlias
99

1010
from .consts import IS_PYDANTIC_V2
11-
from .v1 import BaseModelV1, FieldV1
12-
from .v2 import BaseModelV2, FieldV2
1311

14-
AnyBaseModel: TypeAlias = BaseModelV1 | BaseModelV2
15-
AnyField: TypeAlias = FieldV1 | FieldV2
12+
if TYPE_CHECKING:
13+
from .v1 import BaseModelV1, FieldV1
14+
from .v2 import BaseModelV2, FieldV2
15+
16+
AnyBaseModel: TypeAlias = BaseModelV1 | BaseModelV2
17+
AnyField: TypeAlias = FieldV1 | FieldV2
18+
else:
19+
AnyBaseModel = Any
20+
AnyField = Any
21+
1622

1723
if IS_PYDANTIC_V2:
1824
from .v2 import ConfiguredBaseModelV2 as LatestConfiguredBaseModel

fastapi_pagination/pydantic/v2.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,7 @@ def is_pydantic_v2_model(model_cls: type[Any]) -> TypeIs[type[BaseModelV2]]:
3939
if not IS_PYDANTIC_V2:
4040
return False
4141

42-
from .v1 import BaseModelV1
43-
44-
return not lenient_issubclass(model_cls, BaseModelV1)
42+
return lenient_issubclass(model_cls, BaseModelV2)
4543

4644

4745
class ConfiguredBaseModelV2(BaseModelV2):

tests/test_customization.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,3 +367,12 @@ def test_use_pydantic_v1():
367367
]
368368

369369
assert issubclass(CustomPage, BaseModelV1)
370+
371+
page = CustomPage[int].create(
372+
items=["1", "2", "3"],
373+
params=CustomPage.__params_type__(),
374+
total=3,
375+
)
376+
377+
assert page.items == [1, 2, 3]
378+
assert page.total == 3

0 commit comments

Comments
 (0)