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
Pydantic fails to parse object: invents a new irrelevant object instead #2513
Comments
Hi @HansBrende |
@PrettyWood Although the issue you linked to also involves unions, I don't believe this is the same issue AT ALL... unless I am totally missing something! |
@PrettyWood to give you a better idea of how weird this is, if I change the dictionary order to put the irrelevant field first:
Then the output is correct:
If I preserve the order but add a second irrelevant key to the dictionary:
Then the output is still correct:
|
Yes I know it's the exact same issue that I linked even though it's hard to see the relationship. Pydantic tries to match types in the order of the union. And for this can coerce. |
@PrettyWood Gotcha, that makes sense. Thanks for the explanation! |
I keep it open since I'll need to add extra logic for this in the PR for |
@PrettyWood coming back to this now that I've had some time to mull it over: the real bug in this specific example (in my mind) is not so much the absence of import pydantic
class Item(pydantic.BaseModel):
name: str
print(Item.parse_obj([{'name': 'MY NAME', 'irrelevant': 'foobar'}])) OUTPUTS:
EXPECTED OUTPUT: Validation Error Whereas, swapping the order of the dict to print(Item.parse_obj([{'irrelevant': 'foobar', 'name': 'MY NAME'}])) We get the expected validation error, as we should:
Current behavior is very much non-deterministic (since, for example, the order of objects in a dict is not guaranteed to be consistent between versions of python, or especially if it is parsed from, say, client-supplied json)... which should qualify as a bug in its own right independent of how unions are implemented. |
@PrettyWood here is a rough quick-fix that would completely eliminate this problem. Rather than unconditionally calling def convert_to_a_REASONABLE_dict(input) -> dict:
if not hasattr(input, 'keys'):
raise ValidationError
return dict(input) Quick-fix #2 (preserves ability to coerce lists into dicts):Or, if preserving the ability to construct a dict from a list of tuples is absolutely necessary (which I don't see why it would be--I really like pydantic's lenient mindset in general, but lists should not be coercible into dicts), then the above function could be made slightly more lenient by doing something like: def _checked_kv_pair(kv):
if type(kv) is not dict:
return kv
raise TypeError('Expected a 2-tuple, got a dict')
def convert_to_a_REASONABLE_dict(input) -> dict:
if not hasattr(input, 'keys'):
input = (_checked_kv_pair(kv) for kv in input)
return dict(input) [Note: why did I use the 'keys' attribute to determine whether the input is a mapping? After some experimenting I figured out that the presence of the 'keys' attribute on an object is how the Quick-fix #3 (like #2 but applies to all mappings)For funsies, here is a somewhat pathological tweak to approach #2 which doesn't limit itself exclusively to def _checked_kv_pair(kv):
if not hasattr(kv, 'keys') or not hasattr(kv, '__getitem__'): # short-circuit: cannot be a valid mapping
return kv
# allow edge cases such as a named tuple having an element named "keys":
k, v = kv
try:
if k is kv[0] and v is kv[1]:
return k, v
except:
pass
raise TypeError(f'Expected a 2-tuple, got a {type(kv)}') I can submit a PR for one of these approaches if desired... |
This will be done for v2 #1268 |
@PrettyWood Great to hear! In that case, my approach #2 or #3 (or something similar) could potentially be snuck in before 2.0, since it is not really a breaking change, correct? |
Approach #4: use existing
|
@PrettyWood @samuelcolvin What do you think? Should I submit a PR for one of these approaches? (And if so, which one?) |
This is fixed in v2 |
Checks
Bug
Output of
python -c "import pydantic.utils; print(pydantic.utils.version_info())"
:OUTPUT:
EXPECTED OUTPUT:
EDIT: After discussion & reflection, I think the presence of the
Union
type in my example is a red herring. Here is an even more minimal example (withUnion
absent) that reproduces the fundamental bug here:OUTPUT:
EXPECTED OUTPUT:
ValidationError
Note: if I swap the order of fields in the dict to:
print(Item.parse_obj([{'irrelevant': 'foobar', 'name': 'MY NAME'}]))
, I do get aValidationError
as expected:And if I add or remove a field from the dict (so that the dict no longer contains 2 items), I get the expected
ValidationError
:I have proposed a couple quick-fixes for this bug; I can open a PR for one of them if desired. As pointed out in the comments, I realize this will be fixed automatically in v2 by #1268; however, 3 of my proposed fixes are pretty much backwards-compatible with existing behavior, so I would think one of them could be introduced before v2.
The text was updated successfully, but these errors were encountered: