-
Notifications
You must be signed in to change notification settings - Fork 292
Make validating assignment work properly with allowed extra #766
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -302,13 +302,13 @@ impl Validator for ModelFieldsValidator { | |
) -> ValResult<'data, PyObject> { | ||
let dict: &PyDict = obj.downcast()?; | ||
|
||
let ok = |output: PyObject| { | ||
let get_updated_dict = |output: PyObject| { | ||
dict.set_item(field_name, output)?; | ||
Ok(dict.to_object(py)) | ||
Ok(dict) | ||
}; | ||
|
||
let prepare_result = |result: ValResult<'data, PyObject>| match result { | ||
Ok(output) => ok(output), | ||
Ok(output) => get_updated_dict(output), | ||
Err(ValError::LineErrors(line_errors)) => { | ||
let errors = line_errors | ||
.into_iter() | ||
|
@@ -358,7 +358,7 @@ impl Validator for ModelFieldsValidator { | |
Some(ref validator) => { | ||
prepare_result(validator.validate(py, field_value, &extra, definitions, recursion_guard)) | ||
} | ||
None => ok(field_value.to_object(py)), | ||
None => get_updated_dict(field_value.to_object(py)), | ||
}, | ||
ExtraBehavior::Forbid | ExtraBehavior::Ignore => { | ||
return Err(ValError::new_with_loc( | ||
|
@@ -372,8 +372,24 @@ impl Validator for ModelFieldsValidator { | |
} | ||
}?; | ||
|
||
let new_extra = match &self.extra_behavior { | ||
ExtraBehavior::Allow => { | ||
let non_extra_data = PyDict::new(py); | ||
self.fields.iter().for_each(|f| { | ||
let popped_value = PyAny::get_item(new_data, &f.name).unwrap(); | ||
new_data.del_item(&f.name).unwrap(); | ||
non_extra_data.set_item(&f.name, popped_value).unwrap(); | ||
}); | ||
let new_extra = new_data.copy()?; | ||
new_data.clear(); | ||
new_data.update(non_extra_data.as_mapping())?; | ||
new_extra.to_object(py) | ||
} | ||
_ => py.None(), | ||
}; | ||
|
||
let fields_set: &PySet = PySet::new(py, &[field_name.to_string()])?; | ||
Ok((new_data, py.None(), fields_set.to_object(py)).to_object(py)) | ||
Ok((new_data.to_object(py), new_extra, fields_set.to_object(py)).to_object(py)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The return value of a) Should we change the return value of EDIT based on what I see in the tests, I think the answer to both of these is "yes". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Some historical perspective: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This validate_assignment is also used for dataclasses, and I think we need it to be a |
||
} | ||
|
||
fn different_strict_behavior( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -347,8 +347,8 @@ def test_validate_assignment_allow_extra(): | |
assert v.validate_python({'field_a': 'test'}) == ({'field_a': 'test'}, {}, {'field_a'}) | ||
|
||
assert v.validate_assignment({'field_a': 'test'}, 'other_field', 456) == ( | ||
{'field_a': 'test', 'other_field': 456}, | ||
None, | ||
{'field_a': 'test'}, | ||
{'other_field': 456}, | ||
{'other_field'}, | ||
) | ||
Comment on lines
349
to
353
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Based on this it looks like the type annotation in |
||
|
||
|
@@ -364,8 +364,8 @@ def test_validate_assignment_allow_extra_validate(): | |
) | ||
|
||
assert v.validate_assignment({'field_a': 'test'}, 'other_field', '456') == ( | ||
{'field_a': 'test', 'other_field': 456}, | ||
None, | ||
{'field_a': 'test'}, | ||
{'other_field': 456}, | ||
{'other_field'}, | ||
) | ||
|
||
|
@@ -1682,10 +1682,12 @@ def test_extra_behavior_allow( | |
assert fields_set == {'f', 'extra_field'} | ||
|
||
v.validate_assignment(m, 'f', 'y') | ||
assert m['f'] == 'y' | ||
assert m == {'f': 'y'} | ||
|
||
v.validate_assignment(m, 'not_f', '123') | ||
assert m['not_f'] == expected_extra_value | ||
new_m, new_model_extra, new_fields_set = v.validate_assignment({**m, **model_extra}, 'not_f', '123') | ||
assert new_m == {'f': 'y'} | ||
assert new_model_extra == {'extra_field': expected_extra_value, 'not_f': expected_extra_value} | ||
assert new_fields_set == {'not_f'} | ||
|
||
|
||
@pytest.mark.parametrize( | ||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel there must be a better way to achieve this — @davidhewitt maybe you have some suggestions?
The idea — I'm trying to take
new_data
, which will have all key-value pairs for fields and extra, and split them into two dicts —new_data
which has only field values, andnew_extra
which has everything else.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks pretty sound to me on the whole
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're modifying
new_data
so I don't think you need to create two new dicts. The challenge seems to be that you only have the list of known fields, so you are forced to remove them fromnew_data
.How about swapping the binding over like this:
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we check previously for any possible source of extra keys (and error), so I think we can ignore that potential issue here. So this looks good and we can drop the comment.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just kidding, this breaks some tests because we currently assume in some places that
validate_assignment
does an in-place modification to the fields dict, but this makes it become theextra
dict. I'm not sure what the ideal behavior is here but I am inclined to leave as it was before for now, and we can revisit this in a separate PR if/when it seems worthwhile.