@@ -14,6 +14,7 @@ use crate::recursion_guard::RecursionGuard;
14
14
use super :: function:: convert_err;
15
15
use super :: { build_validator, BuildValidator , CombinedValidator , Definitions , DefinitionsBuilder , Extra , Validator } ;
16
16
17
+ const ROOT_FIELD : & str = "root" ;
17
18
const DUNDER_DICT : & str = "__dict__" ;
18
19
const DUNDER_FIELDS_SET_KEY : & str = "__pydantic_fields_set__" ;
19
20
const DUNDER_MODEL_EXTRA_KEY : & str = "__pydantic_extra__" ;
@@ -52,9 +53,10 @@ pub struct ModelValidator {
52
53
validator : Box < CombinedValidator > ,
53
54
class : Py < PyType > ,
54
55
post_init : Option < Py < PyString > > ,
55
- name : String ,
56
56
frozen : bool ,
57
57
custom_init : bool ,
58
+ root_model : bool ,
59
+ name : String ,
58
60
}
59
61
60
62
impl BuildValidator for ModelValidator {
@@ -87,11 +89,12 @@ impl BuildValidator for ModelValidator {
87
89
post_init : schema
88
90
. get_as :: < & str > ( intern ! ( py, "post_init" ) ) ?
89
91
. map ( |s| PyString :: intern ( py, s) . into_py ( py) ) ,
92
+ frozen : schema. get_as ( intern ! ( py, "frozen" ) ) ?. unwrap_or ( false ) ,
93
+ custom_init : schema. get_as ( intern ! ( py, "custom_init" ) ) ?. unwrap_or ( false ) ,
94
+ root_model : schema. get_as ( intern ! ( py, "root_model" ) ) ?. unwrap_or ( false ) ,
90
95
// Get the class's `__name__`, not using `class.name()` since it uses `__qualname__`
91
96
// which is not what we want here
92
97
name : class. getattr ( intern ! ( py, "__name__" ) ) ?. extract ( ) ?,
93
- frozen : schema. get_as ( intern ! ( py, "frozen" ) ) ?. unwrap_or ( false ) ,
94
- custom_init : schema. get_as ( intern ! ( py, "custom_init" ) ) ?. unwrap_or ( false ) ,
95
98
}
96
99
. into ( ) )
97
100
}
@@ -125,28 +128,24 @@ impl Validator for ModelValidator {
125
128
// mask 0 so JSON is input is never true here
126
129
if input. input_is_instance ( class, 0 ) ? {
127
130
if self . revalidate . should_revalidate ( input, class) {
128
- let fields_set = input. input_get_attr ( intern ! ( py, DUNDER_FIELDS_SET_KEY ) ) . unwrap ( ) ?;
129
-
130
- // get dict here so from_attributes logic doesn't apply
131
- let dict = input. input_get_attr ( intern ! ( py, DUNDER_DICT ) ) . unwrap ( ) ?;
132
- let model_extra = input. input_get_attr ( intern ! ( py, DUNDER_MODEL_EXTRA_KEY ) ) . unwrap ( ) ?;
133
-
134
- let full_model_dict: & PyAny = if model_extra. is_none ( ) {
135
- dict
131
+ if self . root_model {
132
+ let inner_input: & PyAny = input. input_get_attr ( intern ! ( py, ROOT_FIELD ) ) . unwrap ( ) ?;
133
+ self . validate_construct ( py, inner_input, None , extra, definitions, recursion_guard)
136
134
} else {
137
- let full_model_dict = dict. downcast :: < PyDict > ( ) ?. copy ( ) ?;
138
- full_model_dict. update ( model_extra. downcast ( ) ?) ?;
139
- full_model_dict
140
- } ;
141
-
142
- let output = self
143
- . validator
144
- . validate ( py, full_model_dict, extra, definitions, recursion_guard) ?;
145
-
146
- let ( model_dict, model_extra, _) : ( & PyAny , & PyAny , & PyAny ) = output. extract ( py) ?;
147
- let instance = self . create_class ( model_dict, model_extra, fields_set) ?;
148
-
149
- self . call_post_init ( py, instance, input, extra)
135
+ let fields_set = input. input_get_attr ( intern ! ( py, DUNDER_FIELDS_SET_KEY ) ) . unwrap ( ) ?;
136
+ // get dict here so from_attributes logic doesn't apply
137
+ let dict = input. input_get_attr ( intern ! ( py, DUNDER_DICT ) ) . unwrap ( ) ?;
138
+ let model_extra = input. input_get_attr ( intern ! ( py, DUNDER_MODEL_EXTRA_KEY ) ) . unwrap ( ) ?;
139
+
140
+ let inner_input: & PyAny = if model_extra. is_none ( ) {
141
+ dict
142
+ } else {
143
+ let full_model_dict = dict. downcast :: < PyDict > ( ) ?. copy ( ) ?;
144
+ full_model_dict. update ( model_extra. downcast ( ) ?) ?;
145
+ full_model_dict
146
+ } ;
147
+ self . validate_construct ( py, inner_input, Some ( fields_set) , extra, definitions, recursion_guard)
148
+ }
150
149
} else {
151
150
Ok ( input. to_object ( py) )
152
151
}
@@ -158,22 +157,7 @@ impl Validator for ModelValidator {
158
157
input,
159
158
) )
160
159
} else {
161
- if self . custom_init {
162
- // If we wanted, we could introspect the __init__ signature, and store the
163
- // keyword arguments and types, and create a validator for them.
164
- // Perhaps something similar to `validate_call`? Could probably make
165
- // this work with from_attributes, and would essentially allow you to
166
- // handle init vars by adding them to the __init__ signature.
167
- if let Some ( kwargs) = input. as_kwargs ( py) {
168
- return Ok ( self . class . call ( py, ( ) , Some ( kwargs) ) ?) ;
169
- }
170
- }
171
- let output = self
172
- . validator
173
- . validate ( py, input, extra, definitions, recursion_guard) ?;
174
- let ( model_dict, model_extra, fields_set) : ( & PyAny , & PyAny , & PyAny ) = output. extract ( py) ?;
175
- let instance = self . create_class ( model_dict, model_extra, fields_set) ?;
176
- self . call_post_init ( py, instance, input, extra)
160
+ self . validate_construct ( py, input, None , extra, definitions, recursion_guard)
177
161
}
178
162
}
179
163
@@ -189,9 +173,29 @@ impl Validator for ModelValidator {
189
173
) -> ValResult < ' data , PyObject > {
190
174
if self . frozen {
191
175
return Err ( ValError :: new ( ErrorType :: FrozenInstance , field_value) ) ;
176
+ } else if self . root_model {
177
+ return if field_name != ROOT_FIELD {
178
+ Err ( ValError :: new_with_loc (
179
+ ErrorType :: NoSuchAttribute {
180
+ attribute : field_name. to_string ( ) ,
181
+ } ,
182
+ field_value,
183
+ field_name. to_string ( ) ,
184
+ ) )
185
+ } else {
186
+ let field_extra = Extra {
187
+ field_name : Some ( field_name) ,
188
+ ..* extra
189
+ } ;
190
+ let output = self
191
+ . validator
192
+ . validate ( py, field_value, & field_extra, definitions, recursion_guard) ?;
193
+
194
+ force_setattr ( py, model, intern ! ( py, ROOT_FIELD ) , output) ?;
195
+ Ok ( model. into_py ( py) )
196
+ } ;
192
197
}
193
- let dict_py_str = intern ! ( py, DUNDER_DICT ) ;
194
- let dict: & PyDict = model. getattr ( dict_py_str) ?. downcast ( ) ?;
198
+ let dict: & PyDict = model. getattr ( intern ! ( py, DUNDER_DICT ) ) ?. downcast ( ) ?;
195
199
196
200
let new_dict = dict. copy ( ) ?;
197
201
new_dict. set_item ( field_name, field_value) ?;
@@ -216,7 +220,7 @@ impl Validator for ModelValidator {
216
220
}
217
221
let output = output. to_object ( py) ;
218
222
219
- force_setattr ( py, model, dict_py_str , output) ?;
223
+ force_setattr ( py, model, intern ! ( py , DUNDER_DICT ) , output) ?;
220
224
Ok ( model. into_py ( py) )
221
225
}
222
226
@@ -262,11 +266,61 @@ impl ModelValidator {
262
266
let output = self
263
267
. validator
264
268
. validate ( py, input, & new_extra, definitions, recursion_guard) ?;
265
- let ( model_dict, model_extra, fields_set) : ( & PyAny , & PyAny , & PyAny ) = output. extract ( py) ?;
266
- set_model_attrs ( self_instance, model_dict, model_extra, fields_set) ?;
269
+
270
+ if self . root_model {
271
+ force_setattr ( py, self_instance, intern ! ( py, ROOT_FIELD ) , output. as_ref ( py) ) ?;
272
+ } else {
273
+ let ( model_dict, model_extra, fields_set) : ( & PyAny , & PyAny , & PyAny ) = output. extract ( py) ?;
274
+ set_model_attrs ( self_instance, model_dict, model_extra, fields_set) ?;
275
+ }
267
276
self . call_post_init ( py, self_instance. into_py ( py) , input, extra)
268
277
}
269
278
279
+ fn validate_construct < ' s , ' data > (
280
+ & ' s self ,
281
+ py : Python < ' data > ,
282
+ input : & ' data impl Input < ' data > ,
283
+ existing_fields_set : Option < & ' data PyAny > ,
284
+ extra : & Extra ,
285
+ definitions : & ' data Definitions < CombinedValidator > ,
286
+ recursion_guard : & ' s mut RecursionGuard ,
287
+ ) -> ValResult < ' data , PyObject > {
288
+ if self . custom_init {
289
+ // If we wanted, we could introspect the __init__ signature, and store the
290
+ // keyword arguments and types, and create a validator for them.
291
+ // Perhaps something similar to `validate_call`? Could probably make
292
+ // this work with from_attributes, and would essentially allow you to
293
+ // handle init vars by adding them to the __init__ signature.
294
+ if let Some ( kwargs) = input. as_kwargs ( py) {
295
+ return Ok ( self . class . call ( py, ( ) , Some ( kwargs) ) ?) ;
296
+ }
297
+ }
298
+
299
+ let output = if self . root_model {
300
+ let field_extra = Extra {
301
+ field_name : Some ( ROOT_FIELD ) ,
302
+ ..* extra
303
+ } ;
304
+ self . validator
305
+ . validate ( py, input, & field_extra, definitions, recursion_guard) ?
306
+ } else {
307
+ self . validator
308
+ . validate ( py, input, extra, definitions, recursion_guard) ?
309
+ } ;
310
+
311
+ let instance = create_class ( self . class . as_ref ( py) ) ?;
312
+ let instance_ref = instance. as_ref ( py) ;
313
+
314
+ if self . root_model {
315
+ force_setattr ( py, instance_ref, intern ! ( py, ROOT_FIELD ) , output) ?;
316
+ } else {
317
+ let ( model_dict, model_extra, val_fields_set) : ( & PyAny , & PyAny , & PyAny ) = output. extract ( py) ?;
318
+ let fields_set = existing_fields_set. unwrap_or ( val_fields_set) ;
319
+ set_model_attrs ( instance_ref, model_dict, model_extra, fields_set) ?;
320
+ }
321
+ self . call_post_init ( py, instance, input, extra)
322
+ }
323
+
270
324
fn call_post_init < ' s , ' data > (
271
325
& ' s self ,
272
326
py : Python < ' data > ,
@@ -281,13 +335,6 @@ impl ModelValidator {
281
335
}
282
336
Ok ( instance)
283
337
}
284
-
285
- fn create_class ( & self , model_dict : & PyAny , model_extra : & PyAny , fields_set : & PyAny ) -> PyResult < PyObject > {
286
- let py = model_dict. py ( ) ;
287
- let instance = create_class ( self . class . as_ref ( py) ) ?;
288
- set_model_attrs ( instance. as_ref ( py) , model_dict, model_extra, fields_set) ?;
289
- Ok ( instance)
290
- }
291
338
}
292
339
293
340
/// based on the following but with the second argument of new_func set to an empty tuple as required
0 commit comments