Skip to content

Commit 8d2e4ea

Browse files
committed
__pydantic_extra__ None when not used
1 parent 339fa87 commit 8d2e4ea

File tree

7 files changed

+118
-110
lines changed

7 files changed

+118
-110
lines changed

pydantic_core/core_schema.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2807,7 +2807,7 @@ def model_fields_schema(
28072807
)
28082808
v = SchemaValidator(wrapper_schema)
28092809
print(v.validate_python({'a': 'hello'}))
2810-
#> ({'a': 'hello'}, {}, {'a'})
2810+
#> ({'a': 'hello'}, None, {'a'})
28112811
```
28122812
28132813
Args:

src/validators/model.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,17 @@ impl Validator for ModelValidator {
133133
let dict = input.input_get_attr(intern!(py, DUNDER_DICT)).unwrap()?;
134134
let model_extra = input.input_get_attr(intern!(py, DUNDER_MODEL_EXTRA_KEY)).unwrap()?;
135135

136-
let full_model_dict = dict.downcast::<PyDict>()?.copy()?;
137-
full_model_dict.update(model_extra.downcast()?)?;
136+
let full_model_dict: &PyAny = if model_extra.is_none() {
137+
dict
138+
} else {
139+
let full_model_dict = dict.downcast::<PyDict>()?.copy()?;
140+
full_model_dict.update(model_extra.downcast()?)?;
141+
full_model_dict
142+
};
138143

139144
let output = self
140145
.validator
141-
.validate(py, full_model_dict as &PyAny, extra, slots, recursion_guard)?;
146+
.validate(py, full_model_dict, extra, slots, recursion_guard)?;
142147

143148
let (model_dict, model_extra, _): (&PyAny, &PyAny, &PyAny) = output.extract(py)?;
144149
let instance = self.create_class(model_dict, model_extra, fields_set)?;
@@ -185,7 +190,7 @@ impl Validator for ModelValidator {
185190
self.validator
186191
.validate_assignment(py, new_dict, field_name, field_value, extra, slots, recursion_guard)?;
187192

188-
let (output, _, updated_fields_set): (&PyDict, &PyDict, &PySet) = output.extract(py)?;
193+
let (output, _, updated_fields_set): (&PyDict, &PyAny, &PySet) = output.extract(py)?;
189194

190195
if let Ok(fields_set) = model.input_get_attr(intern!(py, DUNDER_FIELDS_SET_KEY)).unwrap() {
191196
let fields_set: &PySet = fields_set.downcast()?;

src/validators/model_fields.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ impl Validator for ModelFieldsValidator {
113113
let dict = input.validate_model_fields(strict, self.from_attributes)?;
114114

115115
let model_dict = PyDict::new(py);
116-
let model_extra_dict = PyDict::new(py);
116+
let mut model_extra_dict_op: Option<&PyDict> = None;
117117
let mut errors: Vec<ValLineError> = Vec::with_capacity(self.fields.len());
118118
let mut fields_set_vec: Vec<Py<PyString>> = Vec::with_capacity(self.fields.len());
119119

@@ -182,6 +182,7 @@ impl Validator for ModelFieldsValidator {
182182
}
183183

184184
if let Some(ref mut used_keys) = used_keys {
185+
let model_extra_dict = PyDict::new(py);
185186
for item_result in <$iter>::new($dict)? {
186187
let (raw_key, value) = item_result?;
187188
let either_str = match raw_key.strict_str() {
@@ -233,6 +234,7 @@ impl Validator for ModelFieldsValidator {
233234
}
234235
}
235236
}
237+
model_extra_dict_op = Some(model_extra_dict);
236238
}
237239
}};
238240
}
@@ -247,7 +249,7 @@ impl Validator for ModelFieldsValidator {
247249
Err(ValError::LineErrors(errors))
248250
} else {
249251
let fields_set = PySet::new(py, &fields_set_vec)?;
250-
Ok((model_dict, model_extra_dict, fields_set).to_object(py))
252+
Ok((model_dict, model_extra_dict_op, fields_set).to_object(py))
251253
}
252254
}
253255

@@ -335,8 +337,7 @@ impl Validator for ModelFieldsValidator {
335337
}?;
336338

337339
let fields_set: &PySet = PySet::new(py, &[field_name.to_string()])?;
338-
let model_extra = PyDict::new(py);
339-
Ok((new_data, model_extra, fields_set.to_object(py)).to_object(py))
340+
Ok((new_data, py.None(), fields_set.to_object(py)).to_object(py))
340341
}
341342

342343
fn different_strict_behavior(

tests/test_validation_context.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,10 @@ def f2(input_value, info):
132132

133133
m1, model_extra, fields_set = v.validate_python({'f1': '1', 'f2': '2'}, strict=None, context={'x': 'y'})
134134
assert m1 == {'f1': "1| context: {'x': 'y', 'f1': '1'}", 'f2': "2| context: {'x': 'y', 'f1': '1', 'f2': '2'}"}
135-
assert model_extra == {}
135+
assert model_extra is None
136136
assert fields_set == {'f1', 'f2'}
137137

138138
m2, model_extra, fields_set = v.validate_assignment(m1, 'f1', '3', context={'x': 'y'})
139139
assert m2 == {'f1': "3| context: {'x': 'y', 'f1': '3'}", 'f2': "2| context: {'x': 'y', 'f1': '1', 'f2': '2'}"}
140-
assert model_extra == {}
140+
assert model_extra is None
141141
assert fields_set == {'f1'}

tests/validators/test_function.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -783,8 +783,8 @@ def f(input_value: Any, info: core_schema.FieldValidationInfo) -> Any:
783783
('before', {'value': {'x': b'input', 'y': '123'}}, {'value': {'x': 'different', 'y': 123}}),
784784
(
785785
'after',
786-
{'value': ({'x': 'input', 'y': 123}, {}, {'y', 'x'})},
787-
{'value': ({'x': 'different', 'y': 123}, {}, {'x'})},
786+
{'value': ({'x': 'input', 'y': 123}, None, {'y', 'x'})},
787+
{'value': ({'x': 'different', 'y': 123}, None, {'x'})},
788788
),
789789
('wrap', {'value': {'x': b'input', 'y': '123'}}, {'value': {'x': 'different', 'y': 123}}),
790790
],

tests/validators/test_model.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class MyModel:
3030
assert isinstance(m, MyModel)
3131
assert m.field_a == 'test'
3232
assert m.field_b == 12
33-
assert m.__pydantic_extra__ == {}
33+
assert m.__pydantic_extra__ is None
3434
assert m.__pydantic_fields_set__ == {'field_a', 'field_b'}
3535
assert m.__dict__ == {'field_a': 'test', 'field_b': 12}
3636

@@ -329,7 +329,7 @@ class MyModel:
329329
'foo': 'apple',
330330
'bar': 123,
331331
'__pydantic_fields_set__': {'foo', 'bar'},
332-
'__pydantic_extra__': {},
332+
'__pydantic_extra__': None,
333333
}
334334

335335
m = v.validate_python({'foo': 'banana', 'spam': [1, 2, 3]})
@@ -339,7 +339,7 @@ class MyModel:
339339
'foo': 'banana',
340340
'spam': [1, 2, 3],
341341
'__pydantic_fields_set__': {'spam', 'foo'},
342-
'__pydantic_extra__': {},
342+
'__pydantic_extra__': None,
343343
}
344344

345345

@@ -678,7 +678,7 @@ def __init__(self, a, b, fields_set):
678678
]
679679

680680
m5 = MyModel('x', 5, None)
681-
with pytest.raises(AttributeError, match="'MyModel' object has no attribute '__pydantic_fields_set__'"):
681+
with pytest.raises(AttributeError, match='__pydantic_fields_set__'):
682682
v.validate_python(m5)
683683

684684

@@ -1092,8 +1092,8 @@ class MyModel:
10921092
[
10931093
(
10941094
core_schema.general_after_validator_function,
1095-
(({'a': 1, 'b': 2}, {}, {'b'}), 'ValidationInfo(config=None, context=None)'),
1096-
(({'a': 10, 'b': 2}, {}, {'a'}), 'ValidationInfo(config=None, context=None)'),
1095+
(({'a': 1, 'b': 2}, None, {'b'}), 'ValidationInfo(config=None, context=None)'),
1096+
(({'a': 10, 'b': 2}, None, {'a'}), 'ValidationInfo(config=None, context=None)'),
10971097
),
10981098
(
10991099
core_schema.general_before_validator_function,
@@ -1196,7 +1196,9 @@ def __init__(self, **kwargs):
11961196
assert m.a == 1
11971197
assert m.b == 2
11981198
assert m.__pydantic_fields_set__ == {'b'}
1199-
assert calls == ["{'validated_data': ValidatedData(model_dict={'a': 1, 'b': 2}, model_extra={}, fields_set={'b'})}"]
1199+
assert calls == [
1200+
"{'validated_data': ValidatedData(model_dict={'a': 1, 'b': 2}, model_extra=None, fields_set={'b'})}"
1201+
]
12001202

12011203

12021204
def test_custom_init_nested():
@@ -1254,7 +1256,7 @@ def __init__(self, **data):
12541256
# insert_assert(calls)
12551257
assert calls == [
12561258
"outer: {'a': 2, 'b': {'b': 3}}",
1257-
"inner: {'validated_data': ValidatedData(model_dict={'a': 1, 'b': 3}, model_extra={}, fields_set={'b'})}",
1259+
"inner: {'validated_data': ValidatedData(model_dict={'a': 1, 'b': 3}, model_extra=None, fields_set={'b'})}",
12581260
]
12591261

12601262

@@ -1287,7 +1289,7 @@ def __init__(self, **kwargs):
12871289

12881290
m = v.validate_python({'a': 2})
12891291
assert m.a == 2
1290-
assert calls == ["ValidatedData(model_dict={'a': 2}, model_extra={}, fields_set={'a'})"]
1292+
assert calls == ["ValidatedData(model_dict={'a': 2}, model_extra=None, fields_set={'a'})"]
12911293

12921294
with pytest.raises(ValidationError, match=r'Field required \[type=missing,'):
12931295
v.validate_python({'validated_data': BadValidatedData({'a': 2}, {'a'})})
@@ -1312,7 +1314,7 @@ def __init__(self, **kwargs):
13121314

13131315
m = v.validate_python({'validated_data': 2})
13141316
assert m.validated_data == 2
1315-
assert calls == ["ValidatedData(model_dict={'validated_data': 2}, model_extra={}, fields_set={'validated_data'})"]
1317+
assert calls == ["ValidatedData(model_dict={'validated_data': 2}, model_extra=None, fields_set={'validated_data'})"]
13161318

13171319
with pytest.raises(ValidationError, match='Input should be a valid integer'):
13181320
v.validate_python({'validated_data': BadValidatedData({'a': 2}, {'a'})})

0 commit comments

Comments
 (0)