55from fastapi import FastAPI , status
66from fastapi .datastructures import QueryParams
77from httpx import AsyncClient
8- from pydantic import BaseModel , ConfigDict , field_validator , model_validator
8+ from pydantic import (
9+ AfterValidator ,
10+ BaseModel ,
11+ BeforeValidator ,
12+ ConfigDict ,
13+ PlainValidator ,
14+ ValidatorFunctionWrapHandler ,
15+ WrapValidator ,
16+ field_validator ,
17+ model_validator ,
18+ )
919from pytest_asyncio import fixture
1020from sqlalchemy .ext .asyncio import AsyncSession
1121
@@ -723,12 +733,23 @@ def validator_post_2(cls, values):
723733 )
724734
725735 async def test_validator_calls_for_field_requests (self , user_1 : User ):
736+ def annotation_pre_validator (value : str ) -> str :
737+ return f"{ value } (annotation_pre_field)"
738+
739+ def annotation_post_validator (value : str ) -> str :
740+ return f"{ value } (annotation_post_field)"
741+
726742 class UserSchemaWithValidator (BaseModel ):
727743 model_config = ConfigDict (
728744 from_attributes = True ,
729745 )
730746
731- name : str
747+ name : Annotated [
748+ str ,
749+ BeforeValidator (annotation_pre_validator ),
750+ AfterValidator (annotation_post_validator ),
751+ # WrapValidator(wrapp_validator),
752+ ]
732753
733754 @field_validator ("name" , mode = "before" )
734755 @classmethod
@@ -749,7 +770,7 @@ def pre_model_validator(cls, data: dict):
749770
750771 @model_validator (mode = "after" )
751772 @classmethod
752- def post_model_validator (self , value ):
773+ def post_model_validator (cls , value ):
753774 value .name = f"{ value .name } (post_model)"
754775 return value
755776
@@ -773,14 +794,89 @@ def post_model_validator(self, value):
773794 "data" : {
774795 "attributes" : {
775796 # check validators call order
776- "name" : f"{ user_1 .name } (pre_model) (pre_field) (post_field) (post_model)" ,
797+ "name" : (
798+ f"{ user_1 .name } (pre_model) (pre_field) (annotation_pre_field) "
799+ "(annotation_post_field) (post_field) (post_model)"
800+ ),
777801 },
778802 "type" : self .resource_type ,
779803 },
780804 "jsonapi" : {"version" : "1.0" },
781805 "meta" : None ,
782806 }
783807
808+ async def test_wrapp_validator_for_field_requests (self , user_1 : User ):
809+ def wrapp_validator (value : str , handler : ValidatorFunctionWrapHandler ) -> str :
810+ return f"{ value } (wrapp_field)"
811+
812+ class UserSchemaWithValidator (BaseModel ):
813+ model_config = ConfigDict (
814+ from_attributes = True ,
815+ )
816+
817+ name : Annotated [str , WrapValidator (wrapp_validator )]
818+
819+ params = QueryParams (
820+ [
821+ (f"fields[{ self .resource_type } ]" , "name" ),
822+ ],
823+ )
824+
825+ app = self .build_app (UserSchemaWithValidator )
826+
827+ async with AsyncClient (app = app , base_url = "http://test" ) as client :
828+ url = app .url_path_for (f"get_{ self .resource_type } _detail" , obj_id = user_1 .id )
829+ res = await client .get (url , params = params )
830+ assert res .status_code == status .HTTP_200_OK , res .text
831+ res_json = res .json ()
832+
833+ assert res_json ["data" ]
834+ assert res_json ["data" ].pop ("id" )
835+ assert res_json == {
836+ "data" : {
837+ "attributes" : {"name" : (f"{ user_1 .name } (wrapp_field)" )},
838+ "type" : self .resource_type ,
839+ },
840+ "jsonapi" : {"version" : "1.0" },
841+ "meta" : None ,
842+ }
843+
844+ async def test_plain_validator_for_field_requests (self , user_1 : User ):
845+ def plain_validator (value : str , handler : ValidatorFunctionWrapHandler ) -> str :
846+ return f"{ value } (plain_field)"
847+
848+ class UserSchemaWithValidator (BaseModel ):
849+ model_config = ConfigDict (
850+ from_attributes = True ,
851+ )
852+
853+ name : Annotated [int , PlainValidator (plain_validator )]
854+
855+ params = QueryParams (
856+ [
857+ (f"fields[{ self .resource_type } ]" , "name" ),
858+ ],
859+ )
860+
861+ app = self .build_app (UserSchemaWithValidator )
862+
863+ async with AsyncClient (app = app , base_url = "http://test" ) as client :
864+ url = app .url_path_for (f"get_{ self .resource_type } _detail" , obj_id = user_1 .id )
865+ res = await client .get (url , params = params )
866+ assert res .status_code == status .HTTP_200_OK , res .text
867+ res_json = res .json ()
868+
869+ assert res_json ["data" ]
870+ assert res_json ["data" ].pop ("id" )
871+ assert res_json == {
872+ "data" : {
873+ "attributes" : {"name" : (f"{ user_1 .name } (plain_field)" )},
874+ "type" : self .resource_type ,
875+ },
876+ "jsonapi" : {"version" : "1.0" },
877+ "meta" : None ,
878+ }
879+
784880
785881class TestValidationUtils :
786882 @pytest .mark .parametrize (
0 commit comments