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

adjust ValidationError message in async validators #14

Closed
PaulBecerra opened this issue Mar 9, 2024 · 2 comments
Closed

adjust ValidationError message in async validators #14

PaulBecerra opened this issue Mar 9, 2024 · 2 comments
Assignees

Comments

@PaulBecerra
Copy link

PaulBecerra commented Mar 9, 2024

pydantic model_validator decorator return the following output when a ValidationError is raised:

{
  "detail": [
    {
      "type": "value_error",
      "loc": [
        "body",
        "bar_list"
      ],
      "msg": "Bar code 'test' is repeated.",
      "input": null,
      "ctx": {
        "code": "test"
      }
    }
  ]
}

but async_model_validator & async_field_validator decorators return the following output:

{
  "detail": [
    {
      "type": "value_error",
      "loc": [
        "__root__"
      ],
      "msg": "1 validation error for Foo\nbar_list\n  Bar code 'test' is repeated. [type=value_error, input_value=None, input_type=NoneType]",
      "input": {
        "bar_list": [
          {
            "code": "test"
          },
          {
            "code": "test"
          }
        ]
      }
    }
  ]
}

example used in fastapi with ensure_request_validation_errors:

class Bar(AsyncValidationModelMixin, BaseModel):
    code: str


class Foo(AsyncValidationModelMixin, BaseModel):
    bar_list: List[Bar]

    # @async_model_validator()
    # @async_field_validator("bar_list")
    @model_validator(mode="after")
    def validate_bar(self) -> None:
        validation_errors = []
        unique_bar_codes = set()
        duplicate_bar_codes = set()

        for bar in self.bar_list:
            if bar.code in unique_bar_codes and bar.code not in duplicate_bar_codes:
                duplicate_bar_codes.add(bar.code)

                validation_errors.append(
                    InitErrorDetails(
                        type=PydanticCustomError(
                            "value_error",
                            "Bar code '{code}' is repeated.",
                            {"code": bar.code},
                        ),
                        loc=["bar_list"],
                    )
                )

            else:
                unique_bar_codes.add(bar.code)

        if validation_errors:
            raise ValidationError.from_exception_data(
                title=self.__class__.__name__, line_errors=validation_errors
            )
@ddanier
Copy link
Member

ddanier commented Mar 28, 2024

I will look into this, thanks for the bug report!

@ddanier ddanier added the bug Something isn't working label Mar 28, 2024
@ddanier ddanier self-assigned this Mar 28, 2024
ddanier added a commit that referenced this issue May 29, 2024
@ddanier
Copy link
Member

ddanier commented May 29, 2024

Sorry for the late reply.

Basically there are multiple things happening here:

First pydantic-async-errors did not allow for the RequestValidationErrors in produces in ensure_request_validation_errors to be prefixed. This is important so the errors are using the same location data. As model_async_validate does not know about where the original data came from this is sadly not possible in an automated way. We are not in the process of handling those parameters in FastAPI any more, so we need to add those details ourselves.

I extended the ensure_request_validation_errors context manager to allow passing a prefix. You can for example use with ensure_request_validation_errors("body"): ... to mimic the behaviour FastAPI would have when using POST body data. See #17 (still work in progress)

Then you are raising a ValidationError in your validator. This is basically undefined behaviour, as the pydantic docs state that:
"validators should either return the parsed value or raise a ValueError or AssertionError (assert statements may be used)."
(see https://docs.pydantic.dev/latest/concepts/validators/#field-validators)

This means that pydantic-async-errors has no means to handle this error and merge the error details into a combined list of errors. Thats just not how things are meant to work here. The validator might raise ONE ValueError or ONE AssertionError, nothing more. ValidationErrors are slightly handled as those (kind of) are ValueError instances (at least they were in pydantic v1 - it seems like v2 mimics this behaviour). This means pydantic-async-errors will see your ValidationError containing a list of errors as ONE ValueError and thus put everything into just one error item in the list. This is also the reason the error details (containg a list of errors in text form) are squashed into the "msg" of this error.

This is the expected behaviour and not actually a bug.

@ddanier ddanier removed the bug Something isn't working label May 29, 2024
ddanier added a commit that referenced this issue May 29, 2024
@ddanier ddanier closed this as completed May 29, 2024
ddanier added a commit that referenced this issue May 29, 2024
…lidation-errors

Better align pydantic and async validation errors
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

No branches or pull requests

2 participants