5
5
from fastapi import FastAPI , status
6
6
from fastapi .datastructures import QueryParams
7
7
from 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
+ )
9
19
from pytest_asyncio import fixture
10
20
from sqlalchemy .ext .asyncio import AsyncSession
11
21
@@ -723,12 +733,23 @@ def validator_post_2(cls, values):
723
733
)
724
734
725
735
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
+
726
742
class UserSchemaWithValidator (BaseModel ):
727
743
model_config = ConfigDict (
728
744
from_attributes = True ,
729
745
)
730
746
731
- name : str
747
+ name : Annotated [
748
+ str ,
749
+ BeforeValidator (annotation_pre_validator ),
750
+ AfterValidator (annotation_post_validator ),
751
+ # WrapValidator(wrapp_validator),
752
+ ]
732
753
733
754
@field_validator ("name" , mode = "before" )
734
755
@classmethod
@@ -749,7 +770,7 @@ def pre_model_validator(cls, data: dict):
749
770
750
771
@model_validator (mode = "after" )
751
772
@classmethod
752
- def post_model_validator (self , value ):
773
+ def post_model_validator (cls , value ):
753
774
value .name = f"{ value .name } (post_model)"
754
775
return value
755
776
@@ -773,14 +794,89 @@ def post_model_validator(self, value):
773
794
"data" : {
774
795
"attributes" : {
775
796
# 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
+ ),
777
801
},
778
802
"type" : self .resource_type ,
779
803
},
780
804
"jsonapi" : {"version" : "1.0" },
781
805
"meta" : None ,
782
806
}
783
807
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
+
784
880
785
881
class TestValidationUtils :
786
882
@pytest .mark .parametrize (
0 commit comments