Skip to content

Support negative indices in serializer include/exclude #627

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

Merged
merged 3 commits into from
May 25, 2023

Conversation

adriangb
Copy link
Member

@adriangb adriangb commented May 24, 2023

Closes pydantic/pydantic#5838

Selected Reviewer: @dmontagu

@codecov-commenter
Copy link

codecov-commenter commented May 24, 2023

Codecov Report

Merging #627 (e8124b9) into main (c9a83c8) will increase coverage by 0.01%.
The diff coverage is 100.00%.

❗ Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please install the Github App Integration for your organization. Read more.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #627      +/-   ##
==========================================
+ Coverage   94.02%   94.04%   +0.01%     
==========================================
  Files          98       98              
  Lines       13283    13324      +41     
  Branches       25       25              
==========================================
+ Hits        12490    12531      +41     
  Misses        787      787              
  Partials        6        6              
Impacted Files Coverage Δ
src/serializers/filter.rs 96.07% <100.00%> (+0.58%) ⬆️
src/serializers/infer.rs 96.73% <100.00%> (+0.01%) ⬆️
src/serializers/type_serializers/function.rs 95.14% <100.00%> (ø)
src/serializers/type_serializers/generator.rs 93.47% <100.00%> (ø)
src/serializers/type_serializers/list.rs 95.83% <100.00%> (ø)
src/serializers/type_serializers/tuple.rs 93.24% <100.00%> (+0.17%) ⬆️

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c9a83c8...e8124b9. Read the comment docs.

@codspeed-hq
Copy link

codspeed-hq bot commented May 24, 2023

CodSpeed Performance Report

Merging #627 negative-include-exclude (e8124b9) will not alter performances.

Summary

🔥 0 improvements
❌ 3 regressions
✅ 117 untouched benchmarks

🆕 1 new benchmarks
⁉️ 0 dropped benchmarks

Benchmarks breakdown

Benchmark main negative-include-exclude Change
test_python_json_list_str 641.4 µs 708.4 µs -10.45%
test_python_json_list_int 637.7 µs 704.5 µs -10.47%
test_python_json_list_none 619.5 µs 686.3 µs -10.78%
🆕 test_filter N/A 101.6 µs N/A

Comment on lines 37 to 58
fn map_negative_indices<'a, 'py: 'a>(
py: Python<'py>,
include_or_exclude: &'a PyAny,
len: Option<usize>,
) -> PyResult<&'a PyAny> {
if let Ok(exclude_dict) = include_or_exclude.downcast::<PyDict>() {
let out = PyDict::new(py);
for (k, v) in exclude_dict.iter() {
out.set_item(map_negative_index(py, k, len)?, v)?;
}
Ok(out)
} else if let Ok(exclude_set) = include_or_exclude.downcast::<PySet>() {
let mut values = Vec::with_capacity(exclude_set.len());
for v in exclude_set {
values.push(map_negative_index(py, v, len)?)
}
Ok(PySet::new(py, values)?)
} else {
// return as is and deal with the error later
Ok(include_or_exclude)
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not particularly efficient. We're downcasting twice (once here, again later) and re-creating the entire iterable (when really we could do it on the fly). One option to change this would be to duplicate the "hellish logic" below (which I wanted to avoid) to handle indexes and keys/fields separately. It might also be good to extract the entirety of include and exclude into rust types upfront so that it's not only cheaper to do this post-processing but also maybe some of the "hellish logic" becomes simpler or at least more understandable via type hints.

A quick win might be to check if there are negative indexes in a first pass and only map them / copy the input if there are. I'm skipping that for now since it requires benchmarks to prove it's even better and is a lot more code/boilerplate.

I think this is good enough (and fast enough) for now, we can come back to those more advanced options later.

@adriangb
Copy link
Member Author

please review cc @dmontagu

dmontagu
dmontagu previously approved these changes May 24, 2023
@dmontagu dmontagu dismissed their stale review May 24, 2023 20:38

Samuel wants to make more changes.

@adriangb
Copy link
Member Author

Seems like this is currently fixing https://github.com/pydantic/pydantic/blob/3ffb0c465a2d30accacbde1509ced0a74ba391e5/tests/test_edge_cases.py#L603 but not the other tests. Need to investigate a bit further (and maybe port them over here)

@samuelcolvin
Copy link
Member

@adriangb I think I've the extra logic should fix more cases. Could you check? Feel free to merge if you're happy.

@adriangb
Copy link
Member Author

Yes will look later tonight / tomorrow morning

Copy link
Member

@samuelcolvin samuelcolvin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to remove map_negative_indices, but feel free to merge if it's working everywhere.

Instead of calling __mod__ on each element the include/exclude set/dict, could we instead do the equivilant of

if thing := filter.get(index):
    return thing
elif thing := filter.get(index - len):
    return thing

E.g. move the conversion logic from the include/exclude argument to the index value.

And thereby avoid re-creating the set and dict? Just an idea for the future.

@adriangb
Copy link
Member Author

I tested in Pydantic and this is now resolving all of the tests there so I'm going to go ahead and merge this. Agreed that there may be better ways but none of them are obviously faster or are much more complex, this seems good enough for now.

@adriangb adriangb merged commit b764c16 into main May 25, 2023
@adriangb adriangb deleted the negative-include-exclude branch May 25, 2023 03:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Handle negative indices in include/exclude
4 participants