flask-pydantic
flask-pydantic copied to clipboard
Pydantic v2's error object contains a ctx field
If you intentionally raise ValueError, a field called ctx seems to be added. An example of pydantic v2 error object.
{'validation_error': {'body_params': [{'ctx': {'error': ValueError()},
'input': 'hawksnowlog3',
'loc': ('name',),
'msg': 'Value error, ',
'type': 'value_error',
'url': 'https://errors.pydantic.dev/2.5/v/value_error'}]}}
An error object is included in ctx and the error object cannot be serialized to dict, resulting in an error. The traceback is this.
Traceback (most recent call last):
File "/Users/kakakikikeke/.local/share/virtualenvs/python-try-aR_k1rUJ/lib/python3.11/site-packages/flask/app.py", line 1463, in wsgi_app
response = self.full_dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/kakakikikeke/.local/share/virtualenvs/python-try-aR_k1rUJ/lib/python3.11/site-packages/flask/app.py", line 872, in full_dispatch_request
rv = self.handle_user_exception(e)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/kakakikikeke/.local/share/virtualenvs/python-try-aR_k1rUJ/lib/python3.11/site-packages/flask/app.py", line 870, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/kakakikikeke/.local/share/virtualenvs/python-try-aR_k1rUJ/lib/python3.11/site-packages/flask/app.py", line 855, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/kakakikikeke/.local/share/virtualenvs/python-try-aR_k1rUJ/lib/python3.11/site-packages/flask_pydantic/core.py", line 250, in wrapper
jsonify({"validation_error": err}), status_code
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/kakakikikeke/.local/share/virtualenvs/python-try-aR_k1rUJ/lib/python3.11/site-packages/flask/json/__init__.py", line 170, in jsonify
return current_app.json.response(*args, **kwargs) # type: ignore[return-value]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/kakakikikeke/.local/share/virtualenvs/python-try-aR_k1rUJ/lib/python3.11/site-packages/flask/json/provider.py", line 216, in response
f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/kakakikikeke/.local/share/virtualenvs/python-try-aR_k1rUJ/lib/python3.11/site-packages/flask/json/provider.py", line 181, in dumps
return json.dumps(obj, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/kakakikikeke/.pyenv/versions/3.11.6/lib/python3.11/json/__init__.py", line 238, in dumps
**kw).encode(obj)
^^^^^^^^^^^
File "/Users/kakakikikeke/.pyenv/versions/3.11.6/lib/python3.11/json/encoder.py", line 200, in encode
chunks = self.iterencode(o, _one_shot=True)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/kakakikikeke/.pyenv/versions/3.11.6/lib/python3.11/json/encoder.py", line 258, in iterencode
return _iterencode(o, 0)
^^^^^^^^^^^^^^^^^
File "/Users/kakakikikeke/.local/share/virtualenvs/python-try-aR_k1rUJ/lib/python3.11/site-packages/flask/json/provider.py", line 121, in _default
raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
I solved it by deleting ctx, is there anything else I can do? #84
Thanks.
@kakakikikeke-fork Hi there, thank you for the issue and also the PR. I did some investigation today and yesterday, it is indeed a valid issue and would occur on not only things like body_params but also things like query params since they are based on the same fundation. The PR #84 is currently failing the unit test on CI, I belive its caused by calling err.get("body_params") on err that does not have "body_params", so request that only has query_params if that makes sense.
@yctomwang I tried supporting parameters other than body_params. I have locally run pytest I have also verified that locally all pytest tests are successful. Thanks.
As a workaround for your custom validations you can use PydanticCustomError
from pydantic_core
.
from pydantic_core import PydanticCustomError
def custom_validator(value: str) -> str:
if value == "bad":
raise PydanticCustomError("bad_str", "bad is not good.")
return value
Instead of removing ctx (or url, or input), it would be better to introduce some arguments to 'validate' decorator, similar to include_context|include_url|include_input and pass it down to ve.errors
@nickzorin yep i have to agree with this, removing the ctx would not be the best idea. I am planning to spend some time eventually to deal with this issue, current we are storing the acutal objects hence the reason for the error apprearing. This will require a bit of rework to get all exist test cases to adapt. Also currently the CI is failing because of the pytest black plugin. For some reason I cannot merge to master to address the CI issue.
Any updates on this? I'm planning to introduce flask-pydantic to my org but feel that with all this extra context we are exposing far more info than is desirable.
Guys, any updates so far?( this is really really frustrating! This issue literally means that you cannot use all the pydantic field_validator
or model_validator
by raising python's AssertionError
or ValueError
. Just a small example:
class RequestBodyModel(BaseModel):
name: str
phone_number: Optional[str] = None
email: Optional[EmailStr] = None
@model_validator(mode='before')
@classmethod
def check_email_or_phone(cls, data: Any) -> Any:
email = data.get('email')
phone_number = data.get('phone_number')
assert bool(email) ^ bool(phone_number), 'Email or phone number must be provided, but not both'
return data
And obviously because of ctx
includes the error as Python object, Flask cannot jsonify
the response. @yctomwang I have a simple suggestion as a maybe workaround, just in order to allow Flask does its' JSON serialization job:
- According to the Pydantic doc,
ctx
is a kinda optional, so I do not see any objections removing it from the resultingdict
- On the other side, again Pydantic doc has very explicit example of customizing pydantic error messages, leveraging custom
convert_errors()
func, which can do anything we want, e.g. convertingAssertionError
/ValueError
exception instances to astr
using builtinrepr()
:
>>> repr(err['body_params'][0]['ctx']['error'])
"AssertionError('Email or phone number must be provided, but not both')"
We literally got the ci fixed this week and can finally merge code into the master. After some careful thinking, i think the remove appraoch in #84 might not be the best. We are planning to get this issue addressed as soon as possible. any ideas are welcome