Skip to content
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

aggregate root validation errors #1586

Merged
merged 4 commits into from Jul 3, 2020

Conversation

beezee
Copy link
Contributor

@beezee beezee commented May 31, 2020

Change Summary

This change enables aggregation of multiple root validation errors.

It turns out deduplication of root validators is necessary in the metaclass, since it appears __new__ is called twice for generics, once at class definition, and another when type arguments are applied (seems like semantically python is creating an on-the-fly subclass with the type variables unified,) the second of which results in all root validators defined on the class itself appearing in a base for the type-arg-unified subclass as well as on the type-arg-unified subclass itself, hence duplicating all of them. The issue is caught by current generic tests, but was masked by the short-circuiting (duplication of root validators was not apparent because root validation was halted after the first failure.)

Added test coverage to make sure multiple root validators can fail simultaneously.

Related issue number

#1571

Checklist

  • Unit tests for the changes exist
  • Tests pass on CI and coverage remains at 100%
  • Documentation reflects the changes where applicable
  • changes/<pull request or issue id>-<github username>.md file added describing change
    (see changes/README.md for details)

@codecov
Copy link

codecov bot commented May 31, 2020

Codecov Report

Merging #1586 into master will not change coverage.
The diff coverage is 100.00%.

@@            Coverage Diff            @@
##            master     #1586   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           21        21           
  Lines         3796      3815   +19     
  Branches       752       759    +7     
=========================================
+ Hits          3796      3815   +19     
Impacted Files Coverage Δ
pydantic/utils.py 100.00% <100.00%> (ø)
pydantic/types.py 100.00% <0.00%> (ø)
pydantic/errors.py 100.00% <0.00%> (ø)
pydantic/fields.py 100.00% <0.00%> (ø)
pydantic/schema.py 100.00% <0.00%> (ø)
pydantic/typing.py 100.00% <0.00%> (ø)
pydantic/networks.py 100.00% <0.00%> (ø)
pydantic/validators.py 100.00% <0.00%> (ø)
pydantic/dataclasses.py 100.00% <0.00%> (ø)
pydantic/env_settings.py 100.00% <0.00%> (ø)
... and 1 more

Continue to review full report at Codecov.

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

@beezee
Copy link
Contributor Author

beezee commented May 31, 2020

Appreciate any suggestions about what documentation to update.

It seems to me the behavior at present is not currently documented, and since the change brings root validators in line with the behavior of attribute validators, I'm having a hard time coming up with an intuitive way to highlight the new behavior.

Is there a place where we call this behavior (aggregation of errors) out for attribute validators explicitly? Perhaps changing the wording to reflect all validators there would be the most impactful way to communicate the new behavior.

@samuelcolvin
Copy link
Member

I would add something to https://pydantic-docs.helpmanual.io/usage/validators/#root-validators

Just a note (!!! note) saying "all will be call regardless" would be useful. Also worth saying root validators can't know whether a previous validator failed (that's the case as far as i know right?).

@beezee
Copy link
Contributor Author

beezee commented Jun 1, 2020

I think this might be captured by changing the language " root validators by default will be called even if field validation fails; this behaviour can be changed by setting the skip_on_failure=True keyword argument to the validator." to " root validators by default will be called even if prior validation fails; this behaviour can be changed by setting the skip_on_failure=True keyword argument to the validator."

The new language expands the scope of "things that can fail prior to a given root validator without preventing that root validator from running", from an original scope of field validators, to a new scope of field validators plus any other root validators that run prior to the root validator in question. It also correctly captures that skip_on_failure will be honored regardless which of these sources the prior failure comes from.

That said if you think the note is worthwhile just for the sake of higher visibility, I'm happy to put one in addition, I can see that the language change I'm suggesting is subtle despite accuracy.

To your last point, while the library doesn't do anything for a user in terms of tracking success or failure of prior root validators, there's technically nothing stopping someone from attaching extra data to the underlying values dictionary as they return updated dictionaries from their root validators, allowing subsequent validators to check for presence of these values as signal of success, or absence as signal of failure. From the view of pydantic, I'd say we're un-opinionated in this respect, it's not impossible but we're not helping you do it in any meaningful way.

Open to your thoughts there and happy to include whatever language around this you might feel worthwhile, fwiw I wouldn't find it problematic to not mention at all given the passive role the library has with respect to this nuance.

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.

try not to write essays, it make reviewing PRs even slower than it already is.

pydantic/main.py Outdated Show resolved Hide resolved
@beezee
Copy link
Contributor Author

beezee commented Jun 4, 2020

Honestly not a response I was expecting - I don't know if you intend hostility here, but this doesn't come off as "contributions welcome" to me

@samuelcolvin
Copy link
Member

Sorry it if it seemed hostile, it wasn't meant as such. Just that I'm dyslexic and long responses take me a long time to parse, as you can see from the volume of issues on pydantic, avoiding such time sinks is helpful.

I actually skim-read your message above, but missed the part about duplication. That's my fault, but also kind of an example of what I was saying.

I'll look into this more soon and let you know what I think.

@beezee
Copy link
Contributor Author

beezee commented Jun 4, 2020

Thanks and sorry if I read wrong. I'll take your feedback to heart and continue striving for brevity without sacrificing precision.

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.

otherwise looks good.

pydantic/main.py Outdated Show resolved Hide resolved
@samuelcolvin
Copy link
Member

awesome, thank you.

@samuelcolvin samuelcolvin merged commit e3c5e1d into pydantic:master Jul 3, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants