diff --git a/nonebot/compat.py b/nonebot/compat.py index df5dbb0d..dc861b3f 100644 --- a/nonebot/compat.py +++ b/nonebot/compat.py @@ -16,6 +16,7 @@ from typing import ( Dict, List, Type, + Union, TypeVar, Callable, Optional, @@ -54,6 +55,7 @@ __all__ = ( "model_config", "model_dump", "type_validate_python", + "type_validate_json", "custom_validation", ) @@ -195,13 +197,27 @@ if PYDANTIC_V2: # pragma: pydantic-v2 include: Optional[Set[str]] = None, exclude: Optional[Set[str]] = None, by_alias: bool = False, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, ) -> Dict[str, Any]: - return model.model_dump(include=include, exclude=exclude, by_alias=by_alias) + return model.model_dump( + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) def type_validate_python(type_: Type[T], data: Any) -> T: """Validate data with given type.""" return TypeAdapter(type_).validate_python(data) + def type_validate_json(type_: Type[T], data: Union[str, bytes]) -> T: + """Validate JSON with given type.""" + return TypeAdapter(type_).validate_json(data) + def __get_pydantic_core_schema__( cls: Type["_CustomValidationClass"], source_type: Any, @@ -226,7 +242,7 @@ if PYDANTIC_V2: # pragma: pydantic-v2 else: # pragma: pydantic-v1 from pydantic import Extra - from pydantic import parse_obj_as + from pydantic import parse_obj_as, parse_raw_as from pydantic import BaseConfig as PydanticConfig from pydantic.fields import FieldInfo as BaseFieldInfo from pydantic.fields import ModelField as BaseModelField @@ -341,13 +357,27 @@ else: # pragma: pydantic-v1 include: Optional[Set[str]] = None, exclude: Optional[Set[str]] = None, by_alias: bool = False, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, ) -> Dict[str, Any]: - return model.dict(include=include, exclude=exclude, by_alias=by_alias) + return model.dict( + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) def type_validate_python(type_: Type[T], data: Any) -> T: """Validate data with given type.""" return parse_obj_as(type_, data) + def type_validate_json(type_: Type[T], data: Union[str, bytes]) -> T: + """Validate JSON with given type.""" + return parse_raw_as(type_, data) + def custom_validation(class_: Type["CVC"]) -> Type["CVC"]: """Do nothing in pydantic v1""" return class_ diff --git a/tests/test_compat.py b/tests/test_compat.py index 3c020312..a50da686 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, Optional from dataclasses import dataclass import pytest @@ -11,6 +11,7 @@ from nonebot.compat import ( PydanticUndefined, model_dump, custom_validation, + type_validate_json, type_validate_python, ) @@ -66,3 +67,26 @@ async def test_custom_validation(): assert type_validate_python(TestModel, {"test": 1}) == TestModel(test=1) assert called == [1, 2] + + +@pytest.mark.asyncio +async def test_validate_json(): + class TestModel(BaseModel): + test1: int + test2: str + test3: bool + test4: dict + test5: list + test6: Optional[int] + + assert type_validate_json( + TestModel, + "{" + ' "test1": 1,' + ' "test2": "2",' + ' "test3": true,' + ' "test4": {},' + ' "test5": [],' + ' "test6": null' + "}", + ) == TestModel(test1=1, test2="2", test3=True, test4={}, test5=[], test6=None)