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 v2's error object contains a ctx field #86

Open
kakakikikeke-fork opened this issue Feb 2, 2024 · 9 comments · May be fixed by #96
Open

Pydantic v2's error object contains a ctx field #86

kakakikikeke-fork opened this issue Feb 2, 2024 · 9 comments · May be fixed by #96
Labels
bug Something isn't working

Comments

@kakakikikeke-fork
Copy link

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.

@yctomwang yctomwang added the bug Something isn't working label Feb 2, 2024
@yctomwang
Copy link
Collaborator

@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.

@kakakikikeke-fork
Copy link
Author

@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.

@nplebanskyi
Copy link

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

@nickzorin
Copy link

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

@yctomwang
Copy link
Collaborator

@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.

@eytanhanig
Copy link

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.

@ghost
Copy link

ghost commented Jul 4, 2024

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:

  1. According to the Pydantic doc, ctx is a kinda optional, so I do not see any objections removing it from the resulting dict
  2. 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. converting AssertionError/ValueError exception instances to a str using builtin repr():
>>> repr(err['body_params'][0]['ctx']['error'])
"AssertionError('Email or phone number must be provided, but not both')"

@yctomwang
Copy link
Collaborator

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

@stevanmilic stevanmilic linked a pull request Oct 8, 2024 that will close this issue
@stevanmilic
Copy link

@yctomwang I've made a PR #96 for addressing this issue. It adds arguments to validate() method that can exclude ctx, input or url values in errors dictionary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
6 participants