🐛 Fix: 兼容 pydantic v2.12 FieldInfo 改动 (#3722)

This commit is contained in:
Ju4tCode
2025-10-13 20:18:37 +09:00
committed by GitHub
parent ca9bb2ceef
commit 07f0cc16a1
4 changed files with 1707 additions and 1243 deletions

View File

@@ -55,7 +55,6 @@ __all__ = (
"Required", "Required",
"TypeAdapter", "TypeAdapter",
"custom_validation", "custom_validation",
"extract_field_info",
"field_validator", "field_validator",
"model_config", "model_config",
"model_dump", "model_dump",
@@ -95,7 +94,7 @@ if PYDANTIC_V2: # pragma: pydantic-v2
DEFAULT_CONFIG = ConfigDict(extra="allow", arbitrary_types_allowed=True) DEFAULT_CONFIG = ConfigDict(extra="allow", arbitrary_types_allowed=True)
"""Default config for validations""" """Default config for validations"""
class FieldInfo(BaseFieldInfo): class FieldInfo(BaseFieldInfo): # pyright: ignore[reportGeneralTypeIssues]
"""FieldInfo class with extra property for compatibility with pydantic v1""" """FieldInfo class with extra property for compatibility with pydantic v1"""
# make default can be positional argument # make default can be positional argument
@@ -115,6 +114,20 @@ if PYDANTIC_V2: # pragma: pydantic-v2
slots = super().__slots__ slots = super().__slots__
return {k: v for k, v in self._attributes_set.items() if k not in slots} return {k: v for k, v in self._attributes_set.items() if k not in slots}
@classmethod
def _inherit_construct(
cls, field_info: Optional[BaseFieldInfo] = None, **kwargs: Any
) -> Self:
init_kwargs = {}
if field_info:
init_kwargs.update(field_info._attributes_set)
init_kwargs.update(kwargs)
instance = cls(**init_kwargs)
if field_info:
instance.metadata = field_info.metadata
return instance
@dataclass @dataclass
class ModelField: class ModelField:
"""ModelField class for compatibility with pydantic v1""" """ModelField class for compatibility with pydantic v1"""
@@ -187,13 +200,6 @@ if PYDANTIC_V2: # pragma: pydantic-v2
"""Validate the value pass to the field.""" """Validate the value pass to the field."""
return self.type_adapter.validate_python(value) return self.type_adapter.validate_python(value)
def extract_field_info(field_info: BaseFieldInfo) -> dict[str, Any]:
"""Get FieldInfo init kwargs from a FieldInfo instance."""
kwargs = field_info._attributes_set.copy()
kwargs["annotation"] = field_info.rebuild_annotation()
return kwargs
def model_fields(model: type[BaseModel]) -> list[ModelField]: def model_fields(model: type[BaseModel]) -> list[ModelField]:
"""Get field list of a model.""" """Get field list of a model."""
@@ -201,7 +207,7 @@ if PYDANTIC_V2: # pragma: pydantic-v2
ModelField._construct( ModelField._construct(
name=name, name=name,
annotation=field_info.rebuild_annotation(), annotation=field_info.rebuild_annotation(),
field_info=FieldInfo(**extract_field_info(field_info)), field_info=FieldInfo._inherit_construct(field_info),
) )
for name, field_info in model.model_fields.items() for name, field_info in model.model_fields.items()
] ]
@@ -294,6 +300,22 @@ else: # pragma: pydantic-v1
default = PydanticUndefined default = PydanticUndefined
super().__init__(default, **kwargs) super().__init__(default, **kwargs)
@classmethod
def _inherit_construct(
cls, field_info: Optional[BaseFieldInfo] = None, **kwargs: Any
):
if field_info:
init_kwargs = {
s: getattr(field_info, s)
for s in field_info.__slots__
if s != "extra"
}
init_kwargs.update(field_info.extra)
else:
init_kwargs = {}
init_kwargs.update(kwargs)
return cls(**init_kwargs)
class ModelField(BaseModelField): class ModelField(BaseModelField):
@classmethod @classmethod
def _construct(cls, name: str, annotation: Any, field_info: FieldInfo) -> Self: def _construct(cls, name: str, annotation: Any, field_info: FieldInfo) -> Self:
@@ -364,15 +386,6 @@ else: # pragma: pydantic-v1
def validate_json(self, value: Union[str, bytes]) -> T: def validate_json(self, value: Union[str, bytes]) -> T:
return type_validate_json(self.type, value) return type_validate_json(self.type, value)
def extract_field_info(field_info: BaseFieldInfo) -> dict[str, Any]:
"""Get FieldInfo init kwargs from a FieldInfo instance."""
kwargs = {
s: getattr(field_info, s) for s in field_info.__slots__ if s != "extra"
}
kwargs.update(field_info.extra)
return kwargs
@overload @overload
def field_validator( def field_validator(
field: str, field: str,
@@ -419,9 +432,7 @@ else: # pragma: pydantic-v1
ModelField._construct( ModelField._construct(
name=model_field.name, name=model_field.name,
annotation=model_field.annotation, annotation=model_field.annotation,
field_info=FieldInfo( field_info=FieldInfo._inherit_construct(model_field.field_info),
**extract_field_info(model_field.field_info),
),
) )
for model_field in model.__fields__.values() for model_field in model.__fields__.values()
] ]

View File

@@ -17,7 +17,7 @@ import anyio
from exceptiongroup import BaseExceptionGroup, catch from exceptiongroup import BaseExceptionGroup, catch
from pydantic.fields import FieldInfo as PydanticFieldInfo from pydantic.fields import FieldInfo as PydanticFieldInfo
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined, extract_field_info from nonebot.compat import FieldInfo, ModelField, PydanticUndefined
from nonebot.consts import ARG_KEY, REJECT_PROMPT_RESULT_KEY from nonebot.consts import ARG_KEY, REJECT_PROMPT_RESULT_KEY
from nonebot.dependencies import Dependent, Param from nonebot.dependencies import Dependent, Param
from nonebot.dependencies.utils import check_field_type from nonebot.dependencies.utils import check_field_type
@@ -194,15 +194,12 @@ class DependParam(Param):
use_cache: bool, use_cache: bool,
validate: Union[bool, PydanticFieldInfo], validate: Union[bool, PydanticFieldInfo],
) -> Self: ) -> Self:
kwargs = {} return cls._inherit_construct(
if isinstance(validate, PydanticFieldInfo): validate if isinstance(validate, PydanticFieldInfo) else None,
kwargs.update(extract_field_info(validate)) dependent=sub_dependent,
use_cache=use_cache,
kwargs["validate"] = bool(validate) validate=bool(validate),
kwargs["dependent"] = sub_dependent )
kwargs["use_cache"] = use_cache
return cls(**kwargs)
@classmethod @classmethod
@override @override

View File

@@ -42,6 +42,8 @@ all = [
[dependency-groups] [dependency-groups]
dev = [ dev = [
{ include-group = "test" },
{ include-group = "docs" },
"ruff >=0.14.0, <0.15.0", "ruff >=0.14.0, <0.15.0",
"nonemoji >=0.1.2, <0.2.0", "nonemoji >=0.1.2, <0.2.0",
"pre-commit >=4.0.0, <5.0.0", "pre-commit >=4.0.0, <5.0.0",
@@ -51,7 +53,7 @@ test = [
"nonebug >=0.4.1, <0.5.0", "nonebug >=0.4.1, <0.5.0",
"wsproto >=1.2.0, <2.0.0", "wsproto >=1.2.0, <2.0.0",
"werkzeug >=2.3.6, <4.0.0", "werkzeug >=2.3.6, <4.0.0",
"pytest-cov >=6.0.0, <7.0.0", "pytest-cov >=7.0.0, <8.0.0",
"pytest-xdist >=3.0.2, <4.0.0", "pytest-xdist >=3.0.2, <4.0.0",
"coverage-conditional-plugin >=0.9.0, <0.10.0", "coverage-conditional-plugin >=0.9.0, <0.10.0",
] ]
@@ -69,7 +71,6 @@ Funding = "https://afdian.com/@nonebot"
[tool.uv] [tool.uv]
required-version = ">=0.8.0" required-version = ">=0.8.0"
default-groups = ["dev", "test", "docs"]
conflicts = [[{ group = "pydantic-v1" }, { group = "pydantic-v2" }]] conflicts = [[{ group = "pydantic-v1" }, { group = "pydantic-v2" }]]
[tool.uv.build-backend] [tool.uv.build-backend]
@@ -143,5 +144,5 @@ reportShadowedImports = false
disableBytesTypePromotions = true disableBytesTypePromotions = true
[build-system] [build-system]
requires = ["uv_build >=0.8.3, <0.9.0"] requires = ["uv_build >=0.8.3, <0.10.0"]
build-backend = "uv_build" build-backend = "uv_build"

2871
uv.lock generated

File diff suppressed because it is too large Load Diff