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

Pydantic Helper Functions Raise json.JSONDecodeError #4985

Closed
5 of 15 tasks
dhruvrajan opened this issue Jan 25, 2023 · 11 comments
Closed
5 of 15 tasks

Pydantic Helper Functions Raise json.JSONDecodeError #4985

dhruvrajan opened this issue Jan 25, 2023 · 11 comments
Assignees
Labels
bug V1 Bug related to Pydantic V1.X

Comments

@dhruvrajan
Copy link

Initial Checks

  • I have searched GitHub for a duplicate issue and I'm sure this is something new
  • I have searched Google & StackOverflow for a solution and couldn't find anything
  • I have read and followed the docs and still think this is a bug
  • I am confident that the issue is with pydantic (not my code, or another library in the ecosystem like FastAPI or mypy)

Description

The error handling docs provide that when an issue is encountered when validating data, a pydantic.ValidationError will be thrown.

However there are cases when a json.JSONDecodeError is thrown. I'm including an example below, ('True' is an invalid boolean in JSON; 'true' is valid).

Raising this issue to suggest that either the error be wrapped by a ValidationError or the docs be updated.

Thanks for the hard work!

Example Code

In [1]: import pydantic

In [2]: pydantic.parse_raw_as(bool, "true")
Out[2]: True

In [3]: pydantic.parse_raw_as(bool, "True")
---------------------------------------------------------------------------
JSONDecodeError                           Traceback (most recent call last)
Cell In [3], line 1
----> 1 pydantic.parse_raw_as(bool, "True")

File ~/.pyenv/versions/3.8.12/lib/python3.8/site-packages/pydantic/tools.py:74, in pydantic.tools.parse_raw_as()

File ~/.pyenv/versions/3.8.12/lib/python3.8/site-packages/pydantic/parse.py:37, in pydantic.parse.load_str_bytes()

File ~/.pyenv/versions/3.8.12/lib/python3.8/json/__init__.py:357, in loads(s, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
    352     del kw['encoding']
    354 if (cls is None and object_hook is None and
    355         parse_int is None and parse_float is None and
    356         parse_constant is None and object_pairs_hook is None and not kw):
--> 357     return _default_decoder.decode(s)
    358 if cls is None:
    359     cls = JSONDecoder

File ~/.pyenv/versions/3.8.12/lib/python3.8/json/decoder.py:337, in JSONDecoder.decode(self, s, _w)
    332 def decode(self, s, _w=WHITESPACE.match):
    333     """Return the Python representation of ``s`` (a ``str`` instance
    334     containing a JSON document).
    335 
    336     """
--> 337     obj, end = self.raw_decode(s, idx=_w(s, 0).end())
    338     end = _w(s, end).end()
    339     if end != len(s):

File ~/.pyenv/versions/3.8.12/lib/python3.8/json/decoder.py:355, in JSONDecoder.raw_decode(self, s, idx)
    353     obj, end = self.scan_once(s, idx)
    354 except StopIteration as err:
--> 355     raise JSONDecodeError("Expecting value", s, err.value) from None
    356 return obj, end

JSONDecodeError: Expecting value: line 1 column 1 (char 0)

Python, Pydantic & OS Version

pydantic version: 1.9.0
            pydantic compiled: True
                 install path: /Users/{username}/.pyenv/versions/3.8.12/lib/python3.8/site-packages/pydantic
               python version: 3.8.12 (default, Oct 17 2022, 16:48:14)  [Clang 13.1.6 (clang-1316.0.21.2.5)]
                     platform: macOS-12.6.2-arm64-arm-64bit
     optional deps. installed: ['typing-extensions']

Affected Components

@dhruvrajan dhruvrajan added bug V1 Bug related to Pydantic V1.X unconfirmed Bug not yet confirmed as valid/applicable labels Jan 25, 2023
@dhruvrajan
Copy link
Author

Digging into the implementation, it seems that parse_raw_as can return any of pydantic.ValidationError, json.JSONDecodeError, TypeError, RuntimeError

@hramezani
Copy link
Member

It's returning json.JSONDecodeError because it can't parse the value "True" to json. so the error happens in converting string to json not in validation or parsing.

You will get a pydantic.ValidationError if you parse a valid json like:

>>> pydantic.parse_raw_as(int, "{}")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/hasan/work/pydantic/pydantic/tools.py", line 82, in parse_raw_as
    return parse_obj_as(type_, obj, type_name=type_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/hasan/work/pydantic/pydantic/tools.py", line 38, in parse_obj_as
    return model_type(__root__=obj).__root__
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/hasan/work/pydantic/pydantic/main.py", line 342, in __init__
    raise validation_error
pydantic.error_wrappers.ValidationError: 1 validation error for ParsingModel[int]
__root__
  value is not a valid integer (type=type_error.integer)

I think we can close this issue because or wrap string conversion error with pydantic.ValidationError which is not good IMHO.

What do you think @samuelcolvin

@dhruvrajan
Copy link
Author

This behavior though, is different than the behaviour with normal model fields. If you passTrueinto a bool or strictbool field within a model, the jsondecodeerror gets wrapped by ValidationError.

Would be good to keep the behaviour consistent.
For my usecase it's important to know which errors might be thrown so that they can be caught explicitly, ideally without simply catching Base exception.

@hramezani
Copy link
Member

This behavior though, is different than the behaviour with normal model fields. If you passTrueinto a bool or strictbool field within a model, the jsondecodeerror gets wrapped by ValidationError.

Are you sure that you get json.JSONDecodeError wrapped by pydantic.ValidationError? Could you provide an example?

@dhruvrajan
Copy link
Author

dhruvrajan commented Jan 26, 2023

Sure, thinking of the following example:

In [1]: import pydantic

In [2]: class Coerced(pydantic.BaseModel):
   ...:     val: bool
   ...: 

In [3]: Coerced.parse_raw("{{\"val\": \"true\"}}")
---------------------------------------------------------------------------
JSONDecodeError                           Traceback (most recent call last)
File ~/.pyenv/versions/3.8.12/lib/python3.8/site-packages/pydantic/main.py:524, in pydantic.main.BaseModel.parse_raw()

File ~/.pyenv/versions/3.8.12/lib/python3.8/site-packages/pydantic/parse.py:37, in pydantic.parse.load_str_bytes()

File ~/.pyenv/versions/3.8.12/lib/python3.8/json/__init__.py:357, in loads(s, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
    354 if (cls is None and object_hook is None and
    355         parse_int is None and parse_float is None and
    356         parse_constant is None and object_pairs_hook is None and not kw):
--> 357     return _default_decoder.decode(s)
    358 if cls is None:

File ~/.pyenv/versions/3.8.12/lib/python3.8/json/decoder.py:337, in JSONDecoder.decode(self, s, _w)
    333 """Return the Python representation of ``s`` (a ``str`` instance
    334 containing a JSON document).
    335 
    336 """
--> 337 obj, end = self.raw_decode(s, idx=_w(s, 0).end())
    338 end = _w(s, end).end()

File ~/.pyenv/versions/3.8.12/lib/python3.8/json/decoder.py:353, in JSONDecoder.raw_decode(self, s, idx)
    352 try:
--> 353     obj, end = self.scan_once(s, idx)
    354 except StopIteration as err:

JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)

During handling of the above exception, another exception occurred:

ValidationError                           Traceback (most recent call last)
Cell In [3], line 1
----> 1 Coerced.parse_raw("{{\"val\": \"true\"}}")

File ~/.pyenv/versions/3.8.12/lib/python3.8/site-packages/pydantic/main.py:533, in pydantic.main.BaseModel.parse_raw()

ValidationError: 1 validation error for Coerced
__root__
  Expecting property name enclosed in double quotes: line 1 column 2 (char 1) (type=value_error.jsondecode; msg=Expecting property name enclosed in double quotes; doc={{"val": "true"}}; pos=1; lineno=1; colno=2)

Same json.JSONDecodeError, but it's wrapped by a pydantic.ValidationError, which is more convenient for exception handling.

@samuelcolvin samuelcolvin removed the unconfirmed Bug not yet confirmed as valid/applicable label Jan 26, 2023
@samuelcolvin
Copy link
Member

Confirmed, thanks for reporting.

Question is whether fixing this in V1.10 would qualify as a breaking change? I think it might.

it'll definitely be fixed in V2 as all JSON parsing happens in pydantic-core, and parse_raw_as will be replaced by validate, see #4669.

@samuelcolvin samuelcolvin added this to the Version 2 Issues milestone Jan 26, 2023
@dhruvrajan
Copy link
Author

Thanks @samuelcolvin — that's cool to hear about V2!

I think it would be good to update this in V1.10—anyone explicitly catching json.JSONDecodeError must also be catching ValidationError, so I wouldn't consider it a breaking change. It's significantly better to know which exceptions might be thrown—otherwise, you have to catch Exception everywhere, which isn't great practice.

Happy to provide the PR if you agree.

@samuelcolvin
Copy link
Member

Well, you don't need to catch Exception, both json.JSONDecodeError and ValidatoinError inherit from ValueError I believe.

More specifcally you can catch each separately.

I'll see what others say, I guess I'm neutral, on adding a fix to V2, virtually anything can be a breaking that at this point.

@hramezani
Copy link
Member

Thanks @dhruvrajan for clearing this up.
Agree with @samuelcolvin, it will be a breaking change in 1.10.x. Will be fixed in V2

@dhruvrajan
Copy link
Author

All makes sense!—That's a good tip, I'll use ValueError then. Thanks for the help.

Looking forward to the V2 release! Feel free to close the issue.

@dmontagu
Copy link
Contributor

It seems to me this issue has been resolved in v2 so I'm going to close this now. If you notice related issues in v2 we'd appreciate the reports!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug V1 Bug related to Pydantic V1.X
Projects
None yet
Development

No branches or pull requests

5 participants