mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
fix: keep the order of the fields when validate_assignment is set (#2075)
Following #2000 and #2046 we can't `pop` the current value when assigning a new one (which was probably the most efficient) as we want to keep the order in the `__dict__`. So let's do a shallow copy of the `__dict__` without the current value fix #2073
This commit is contained in:
@@ -0,0 +1 @@
|
||||
fix: keep the order of the fields when `validate_assignment` is set
|
||||
+8
-8
@@ -384,14 +384,14 @@ class BaseModel(Representation, metaclass=ModelMetaclass):
|
||||
|
||||
known_field = self.__fields__.get(name, None)
|
||||
if known_field:
|
||||
original_value = self.__dict__.pop(name)
|
||||
try:
|
||||
value, error_ = known_field.validate(value, self.__dict__, loc=name, cls=self.__class__)
|
||||
if error_:
|
||||
raise ValidationError([error_], self.__class__)
|
||||
except Exception:
|
||||
self.__dict__[name] = original_value
|
||||
raise
|
||||
# We want to
|
||||
# - make sure validators are called without the current value for this field inside `values`
|
||||
# - keep other values (e.g. submodels) untouched (using `BaseModel.dict()` will change them into dicts)
|
||||
# - keep the order of the fields
|
||||
dict_without_original_value = {k: v for k, v in self.__dict__.items() if k != name}
|
||||
value, error_ = known_field.validate(value, dict_without_original_value, loc=name, cls=self.__class__)
|
||||
if error_:
|
||||
raise ValidationError([error_], self.__class__)
|
||||
else:
|
||||
new_values[name] = value
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import Dict, List, Optional, Tuple
|
||||
|
||||
import pytest
|
||||
|
||||
from pydantic import BaseModel, ConfigError, Extra, ValidationError, errors, validator
|
||||
from pydantic import BaseModel, ConfigError, Extra, Field, ValidationError, errors, validator
|
||||
from pydantic.class_validators import make_generic_validator, root_validator
|
||||
from pydantic.typing import Literal
|
||||
|
||||
@@ -1162,7 +1162,8 @@ def test_field_that_is_being_validated_is_excluded_from_validator_values(mocker)
|
||||
|
||||
class Model(BaseModel):
|
||||
foo: str
|
||||
bar: str
|
||||
bar: str = Field(alias='pika')
|
||||
baz: str
|
||||
|
||||
class Config:
|
||||
validate_assignment = True
|
||||
@@ -1170,12 +1171,27 @@ def test_field_that_is_being_validated_is_excluded_from_validator_values(mocker)
|
||||
@validator('foo')
|
||||
def validate_foo(cls, v, values):
|
||||
check_values({**values})
|
||||
return v
|
||||
|
||||
model = Model(foo='foo_value', bar='bar_value')
|
||||
@validator('bar')
|
||||
def validate_bar(cls, v, values):
|
||||
check_values({**values})
|
||||
return v
|
||||
|
||||
model = Model(foo='foo_value', pika='bar_value', baz='baz_value')
|
||||
check_values.reset_mock()
|
||||
|
||||
assert list(dict(model).items()) == [('foo', 'foo_value'), ('bar', 'bar_value'), ('baz', 'baz_value')]
|
||||
|
||||
model.foo = 'new_foo_value'
|
||||
check_values.assert_called_once_with({'bar': 'bar_value'})
|
||||
check_values.assert_called_once_with({'bar': 'bar_value', 'baz': 'baz_value'})
|
||||
check_values.reset_mock()
|
||||
|
||||
model.bar = 'new_bar_value'
|
||||
check_values.assert_called_once_with({'foo': 'new_foo_value', 'baz': 'baz_value'})
|
||||
|
||||
# ensure field order is the same
|
||||
assert list(dict(model).items()) == [('foo', 'new_foo_value'), ('bar', 'new_bar_value'), ('baz', 'baz_value')]
|
||||
|
||||
|
||||
def test_exceptions_in_field_validators_restore_original_field_value():
|
||||
|
||||
Reference in New Issue
Block a user