mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-09-07 04:26:45 +00:00
✨ Feature: 兼容 Pydantic v2 (#2544)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
@ -22,5 +22,8 @@ rules =
|
||||
"sys_platform != 'linux'": py-linux
|
||||
"sys_platform != 'darwin'": py-darwin
|
||||
"sys_version_info < (3, 9)": py-gte-39
|
||||
"sys_version_info >= (3, 9)": py-lt-39
|
||||
"sys_version_info < (3, 11)": py-gte-311
|
||||
"sys_version_info >= (3, 11)": py-lt-311
|
||||
"package_version('pydantic') < (2,)": pydantic-v2
|
||||
"package_version('pydantic') >= (2,)": pydantic-v1
|
||||
|
@ -4,3 +4,7 @@ from typing import Union
|
||||
async def exc(e: Exception, x: Union[ValueError, TypeError]) -> Exception:
|
||||
assert e == x
|
||||
return e
|
||||
|
||||
|
||||
async def legacy_exc(exception) -> Exception:
|
||||
return exception
|
||||
|
@ -1,8 +1,9 @@
|
||||
import pytest
|
||||
from pydantic import ValidationError, parse_obj_as
|
||||
from pydantic import ValidationError
|
||||
|
||||
from nonebot.adapters import Message
|
||||
from nonebot.compat import type_validate_python
|
||||
from utils import FakeMessage, FakeMessageSegment
|
||||
from nonebot.adapters import Message, MessageSegment
|
||||
|
||||
|
||||
def test_segment_data():
|
||||
@ -47,16 +48,21 @@ def test_segment_add():
|
||||
|
||||
|
||||
def test_segment_validate():
|
||||
assert parse_obj_as(
|
||||
assert type_validate_python(
|
||||
FakeMessageSegment,
|
||||
{"type": "text", "data": {"text": "text"}, "extra": "should be ignored"},
|
||||
) == FakeMessageSegment.text("text")
|
||||
with pytest.raises(ValidationError):
|
||||
type_validate_python(
|
||||
type("FakeMessageSegment2", (MessageSegment,), {}),
|
||||
FakeMessageSegment.text("text"),
|
||||
)
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
parse_obj_as(FakeMessageSegment, "some str")
|
||||
type_validate_python(FakeMessageSegment, "some str")
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
parse_obj_as(FakeMessageSegment, {"data": {}})
|
||||
type_validate_python(FakeMessageSegment, {"data": {}})
|
||||
|
||||
|
||||
def test_segment_join():
|
||||
@ -144,26 +150,26 @@ def test_message_getitem():
|
||||
|
||||
|
||||
def test_message_validate():
|
||||
assert parse_obj_as(FakeMessage, FakeMessage([])) == FakeMessage([])
|
||||
assert type_validate_python(FakeMessage, FakeMessage([])) == FakeMessage([])
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
parse_obj_as(type("FakeMessage2", (Message,), {}), FakeMessage([]))
|
||||
type_validate_python(type("FakeMessage2", (Message,), {}), FakeMessage([]))
|
||||
|
||||
assert parse_obj_as(FakeMessage, "text") == FakeMessage(
|
||||
assert type_validate_python(FakeMessage, "text") == FakeMessage(
|
||||
[FakeMessageSegment.text("text")]
|
||||
)
|
||||
|
||||
assert parse_obj_as(
|
||||
assert type_validate_python(
|
||||
FakeMessage, {"type": "text", "data": {"text": "text"}}
|
||||
) == FakeMessage([FakeMessageSegment.text("text")])
|
||||
|
||||
assert parse_obj_as(
|
||||
assert type_validate_python(
|
||||
FakeMessage,
|
||||
[FakeMessageSegment.text("text"), {"type": "text", "data": {"text": "text"}}],
|
||||
) == FakeMessage([FakeMessageSegment.text("text"), FakeMessageSegment.text("text")])
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
parse_obj_as(FakeMessage, object())
|
||||
type_validate_python(FakeMessage, object())
|
||||
|
||||
|
||||
def test_message_contains():
|
||||
|
56
tests/test_compat.py
Normal file
56
tests/test_compat.py
Normal file
@ -0,0 +1,56 @@
|
||||
from typing import Any
|
||||
from dataclasses import dataclass
|
||||
|
||||
import pytest
|
||||
|
||||
from nonebot.compat import (
|
||||
DEFAULT_CONFIG,
|
||||
Required,
|
||||
FieldInfo,
|
||||
PydanticUndefined,
|
||||
custom_validation,
|
||||
type_validate_python,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_default_config():
|
||||
assert DEFAULT_CONFIG.get("extra") == "allow"
|
||||
assert DEFAULT_CONFIG.get("arbitrary_types_allowed") is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_field_info():
|
||||
# required should be convert to PydanticUndefined
|
||||
assert FieldInfo(Required).default is PydanticUndefined
|
||||
|
||||
# field info should allow extra attributes
|
||||
assert FieldInfo(test="test").extra["test"] == "test"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_custom_validation():
|
||||
called = []
|
||||
|
||||
@custom_validation
|
||||
@dataclass
|
||||
class TestModel:
|
||||
test: int
|
||||
|
||||
@classmethod
|
||||
def __get_validators__(cls):
|
||||
yield cls._validate_1
|
||||
yield cls._validate_2
|
||||
|
||||
@classmethod
|
||||
def _validate_1(cls, v: Any) -> Any:
|
||||
called.append(1)
|
||||
return v
|
||||
|
||||
@classmethod
|
||||
def _validate_2(cls, v: Any) -> Any:
|
||||
called.append(2)
|
||||
return cls(test=v["test"])
|
||||
|
||||
assert type_validate_python(TestModel, {"test": 1}) == TestModel(test=1)
|
||||
assert called == [1, 2]
|
@ -218,7 +218,7 @@ async def test_event(app: App):
|
||||
|
||||
async with app.test_dependent(union_event, allow_types=[EventParam]) as ctx:
|
||||
ctx.pass_params(event=fake_fooevent)
|
||||
ctx.should_return(fake_event)
|
||||
ctx.should_return(fake_fooevent)
|
||||
|
||||
async with app.test_dependent(generic_event, allow_types=[EventParam]) as ctx:
|
||||
ctx.pass_params(event=fake_event)
|
||||
@ -529,13 +529,17 @@ async def test_arg(app: App):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_exception(app: App):
|
||||
from plugins.param.param_exception import exc
|
||||
from plugins.param.param_exception import exc, legacy_exc
|
||||
|
||||
exception = ValueError("test")
|
||||
async with app.test_dependent(exc, allow_types=[ExceptionParam]) as ctx:
|
||||
ctx.pass_params(exception=exception)
|
||||
ctx.should_return(exception)
|
||||
|
||||
async with app.test_dependent(legacy_exc, allow_types=[ExceptionParam]) as ctx:
|
||||
ctx.pass_params(exception=exception)
|
||||
ctx.should_return(exception)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_default(app: App):
|
||||
|
Reference in New Issue
Block a user