@@ -72,6 +72,99 @@ class MyModel:
72
72
assert m .__dict__ == {'field_a' : 'test' , 'field_b' : 12 }
73
73
74
74
75
+ def test_model_class_extra_forbid ():
76
+ class MyModel :
77
+ class Meta :
78
+ pass
79
+
80
+ # this is not required, but it avoids `__pydantic_fields_set__` being included in `__dict__`
81
+ __slots__ = '__dict__' , '__pydantic_fields_set__' , '__pydantic_extra__' , '__pydantic_private__'
82
+ field_a : str
83
+ field_b : int
84
+
85
+ class Wrapper :
86
+ def __init__ (self , inner ):
87
+ self ._inner = inner
88
+
89
+ def __dir__ (self ):
90
+ return dir (self ._inner )
91
+
92
+ def __getattr__ (self , key ):
93
+ return getattr (self ._inner , key )
94
+
95
+ v = SchemaValidator (
96
+ core_schema .model_schema (
97
+ MyModel ,
98
+ core_schema .model_fields_schema (
99
+ {
100
+ 'field_a' : core_schema .model_field (core_schema .str_schema ()),
101
+ 'field_b' : core_schema .model_field (core_schema .int_schema ()),
102
+ },
103
+ extra_behavior = 'forbid' ,
104
+ ),
105
+ )
106
+ )
107
+ m = v .validate_python ({'field_a' : 'test' , 'field_b' : 12 })
108
+ assert isinstance (m , MyModel )
109
+ assert m .field_a == 'test'
110
+ assert m .field_b == 12
111
+
112
+ # try revalidating from the model's attributes
113
+ m = v .validate_python (Wrapper (m ), from_attributes = True )
114
+
115
+ with pytest .raises (ValidationError ) as exc_info :
116
+ m = v .validate_python ({'field_a' : 'test' , 'field_b' : 12 , 'field_c' : 'extra' })
117
+
118
+ assert exc_info .value .errors (include_url = False ) == [
119
+ {'type' : 'extra_forbidden' , 'loc' : ('field_c' ,), 'msg' : 'Extra inputs are not permitted' , 'input' : 'extra' }
120
+ ]
121
+
122
+
123
+ @pytest .mark .parametrize ('extra_behavior' , ['allow' , 'ignore' , 'forbid' ])
124
+ def test_model_class_extra_forbid_from_attributes (extra_behavior : str ):
125
+ # iterating attributes includes much more than just __dict__, so need
126
+ # careful interaction with __extra__
127
+
128
+ class MyModel :
129
+ # this is not required, but it avoids `__pydantic_fields_set__` being included in `__dict__`
130
+ __slots__ = '__dict__' , '__pydantic_fields_set__' , '__pydantic_extra__' , '__pydantic_private__'
131
+ field_a : str
132
+ field_b : int
133
+
134
+ class Data :
135
+ # https://github.com/pydantic/pydantic/issues/9242
136
+ class Meta :
137
+ pass
138
+
139
+ def __init__ (self , ** values ):
140
+ self .__dict__ .update (values )
141
+
142
+ v = SchemaValidator (
143
+ core_schema .model_schema (
144
+ MyModel ,
145
+ core_schema .model_fields_schema (
146
+ {
147
+ 'field_a' : core_schema .model_field (core_schema .str_schema ()),
148
+ 'field_b' : core_schema .model_field (core_schema .int_schema ()),
149
+ },
150
+ extra_behavior = extra_behavior ,
151
+ from_attributes = True ,
152
+ ),
153
+ )
154
+ )
155
+ m = v .validate_python (Data (field_a = 'test' , field_b = 12 ))
156
+ assert isinstance (m , MyModel )
157
+ assert m .field_a == 'test'
158
+ assert m .field_b == 12
159
+
160
+ # with from_attributes, extra is basically ignored
161
+ m = v .validate_python (Data (field_a = 'test' , field_b = 12 , field_c = 'extra' ))
162
+ assert isinstance (m , MyModel )
163
+ assert m .field_a == 'test'
164
+ assert m .field_b == 12
165
+ assert not hasattr (m , 'field_c' )
166
+
167
+
75
168
def test_model_class_setattr ():
76
169
setattr_calls = []
77
170
0 commit comments