mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-10-14 06:36:40 +00:00
🐛 Fix: 兼容 pydantic v2.12 FieldInfo
改动 (#3722)
This commit is contained in:
@@ -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()
|
||||||
]
|
]
|
||||||
|
@@ -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
|
||||||
|
@@ -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"
|
||||||
|
Reference in New Issue
Block a user