Skip to content

Commit c7d5d44

Browse files
committed
fix(core): avoid pydantic.v1 imports under pydantic v2
1 parent 840cf2a commit c7d5d44

File tree

2 files changed

+50
-9
lines changed

2 files changed

+50
-9
lines changed

langfuse/api/core/pydantic_utilities.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
# nopycln: file
44
import datetime as dt
5+
import types
6+
import typing
57
from collections import defaultdict
68
from typing import (
79
Any,
@@ -20,21 +22,35 @@
2022
)
2123

2224
import pydantic
25+
import typing_extensions
2326

2427
IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.")
2528

2629
if IS_PYDANTIC_V2:
27-
from pydantic.v1.datetime_parse import parse_date as parse_date
28-
from pydantic.v1.datetime_parse import parse_datetime as parse_datetime
29-
from pydantic.v1.fields import ModelField as ModelField
30-
from pydantic.v1.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore[attr-defined]
31-
from pydantic.v1.typing import get_args as get_args
32-
from pydantic.v1.typing import get_origin as get_origin
33-
from pydantic.v1.typing import is_literal_type as is_literal_type
34-
from pydantic.v1.typing import is_union as is_union
30+
from pydantic.fields import FieldInfo as FieldInfo
31+
32+
ModelField = FieldInfo # type: ignore[assignment]
33+
encoders_by_type: Dict[Any, Callable[[Any], Any]] = {}
34+
get_args = typing_extensions.get_args
35+
get_origin = typing_extensions.get_origin
36+
37+
def parse_date(value: Any) -> dt.date:
38+
return pydantic.TypeAdapter(dt.date).validate_python(value) # type: ignore[attr-defined]
39+
40+
def parse_datetime(value: Any) -> dt.datetime:
41+
return pydantic.TypeAdapter(dt.datetime).validate_python(value) # type: ignore[attr-defined]
42+
43+
def is_literal_type(type_: Any) -> bool:
44+
origin = typing_extensions.get_origin(type_)
45+
return origin in (typing.Literal, typing_extensions.Literal)
46+
47+
def is_union(type_: Any) -> bool:
48+
origin = typing_extensions.get_origin(type_)
49+
return origin in (Union, types.UnionType)
3550
else:
3651
from pydantic.datetime_parse import parse_date as parse_date # type: ignore[no-redef]
3752
from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore[no-redef]
53+
from pydantic.fields import FieldInfo as FieldInfo
3854
from pydantic.fields import ModelField as ModelField # type: ignore[attr-defined, no-redef]
3955
from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore[no-redef]
4056
from pydantic.typing import get_args as get_args # type: ignore[no-redef]
@@ -287,7 +303,7 @@ def decorator(func: AnyCallable) -> AnyCallable:
287303
return decorator
288304

289305

290-
PydanticField = Union[ModelField, pydantic.fields.FieldInfo]
306+
PydanticField = Union[ModelField, FieldInfo]
291307

292308

293309
def _get_model_fields(model: Type["Model"]) -> Mapping[str, PydanticField]:

tests/test_pydantic_compat.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from __future__ import annotations
2+
3+
import subprocess
4+
import sys
5+
from datetime import date, datetime, timezone
6+
7+
from langfuse.api.core.pydantic_utilities import parse_date, parse_datetime
8+
9+
10+
def test_import_langfuse_with_user_warnings_as_errors() -> None:
11+
result = subprocess.run(
12+
[sys.executable, "-W", "error::UserWarning", "-c", "import langfuse"],
13+
capture_output=True,
14+
text=True,
15+
check=False,
16+
)
17+
18+
assert result.returncode == 0, result.stderr or result.stdout
19+
20+
21+
def test_parse_helpers_support_pydantic_v2() -> None:
22+
assert parse_date("2024-01-02") == date(2024, 1, 2)
23+
assert parse_datetime("2024-01-02T03:04:05Z") == datetime(
24+
2024, 1, 2, 3, 4, 5, tzinfo=timezone.utc
25+
)

0 commit comments

Comments
 (0)