-
-
Notifications
You must be signed in to change notification settings - Fork 389
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
Question: How to handle custom input validation errors in @field_validator #825
Comments
I'm running into this same issue on [{'type': 'value_error', 'loc': ('body', 'payload', 'query'), 'msg': "Value error, Couldn't parse search query.", 'ctx': {'error': ValueError("Couldn't parse search query.")}}] It seems if the |
Yeah I dug into it a bit as well. Either the JSON encoder or in I guess doing a Also, wouldn't it be a bit more safe to be explicit and pick out relevant keys instead of doing Source: https://github.com/vitalik/django-ninja/blob/master/ninja/operation.py#L226-L237 |
This is how I decided to implement this for now: import uuid
from django.contrib.auth.password_validation import validate_password
from django.core.validators import validate_email
from ninja import Router, Schema
from pydantic import constr, field_validator
from django.core.exceptions import ValidationError as DjangoValidationError
from pydantic_core import PydanticCustomError
from users import models
from django.utils.translation import gettext as _
router = Router()
class UserMeInSchema(Schema):
email: constr(to_lower=True) = None
first_name: str
last_name: str
password: str
@field_validator("email")
@classmethod
def email_validator(cls, v: str):
try:
validate_email(v)
except DjangoValidationError as e:
raise PydanticCustomError(
e.code,
e.message,
)
if models.User.objects.filter(email=v).exists():
raise PydanticCustomError(
"unique",
_("A user with this email already exists"),
)
return v
@field_validator("password")
@classmethod
def password_validator(cls, v: str):
try:
validate_password(v)
except DjangoValidationError as e:
raise PydanticCustomError(
"too_easy_password",
_("This password is too easy to guess. Please create a new one."),
{"errors": e.messages},
)
return v
class UserMeOutSchema(Schema):
id: uuid.UUID
email: str
first_name: str
last_name: str
@router.post("/register", response=UserMeOutSchema)
def register(request, data: UserMeInSchema):
user = models.User.objects.create_user(
first_name=data.first_name,
last_name=data.last_name,
email=data.email,
password=data.password,
)
return user Sample output: {
"detail": [
{
"type": "unique",
"loc": [
"body",
"data",
"email"
],
"msg": "A user with this email already exists"
},
{
"type": "string_type",
"loc": [
"body",
"data",
"last_name"
],
"msg": "Input should be a valid string"
},
{
"type": "too_easy_password",
"loc": [
"body",
"data",
"password"
],
"msg": "This password is too easy to guess. Please create a new one.",
"ctx": {
"errors": [
"This password is too short. It must contain at least 8 characters.",
"This password is too common.",
"This password is entirely numeric."
]
}
}
]
} For clarity: I think a single error in The discussion about handling ValueError is still relevant though I guess. |
could you share all your versions (django, python, pydantic, ninja) - cannot reproduce the error on my enviroment generally speaking you throw either keep in mind that you should pass string and not list to value error : class UserMeInSchema(Schema):
email: constr(to_lower=True) = None
first_name: str
last_name: str
password: str
@field_validator("email")
@classmethod
def email_validator(cls, v: str):
try:
validate_email(v)
except DjangoValidationError as e:
raise ValueError(', '.join(e.messages)). # <--------------- join
if xxxx:
raise ValueError("A user with this email already exists")
return v
@field_validator("password")
@classmethod
def password_validator(cls, v: str):
try:
validate_password(v)
except DjangoValidationError as e:
raise ValueError(', '.join(e.messages)) # <--------------- join
return v |
Python 3.10.4 In my case, I saw the same error when passing just a string e.g. Thanks, I will try @SRautila 's suggestion for now. |
Python 3.11.4
|
@vitalik I'm not sure when this changed, but this behavior of pydantic is documented here: https://docs.pydantic.dev/latest/errors/errors/#custom-errors Note the |
This should go away when vitalik/django-ninja#825 is resolved.
this ctx behaviour introduced in fixed try with |
That was fast! Thanks! Regarding best practise, I'm till considering sticking to from django.utils.translation import gettext as _
...
@field_validator("email")
@classmethod
def email_validator(cls, v: str):
if models.User.objects.filter(email=v).exists():
raise ValueError(_("A user with this email already exists"))
return v Swedish output: {
"type": "value_error",
"loc": [
"body",
"data",
"email"
],
"msg": "Value error, En användare med den här eposten finns redan",
"ctx": {
"error": "En användare med den här eposten finns redan"
}
}, So now frontend either needs to use But issue closed. Thanks! |
Hey. I'm a long-time DRF user trying out django-ninja 1.0a2 with pydantic 2.1 and I'm curious about some best practises.
What I'm doing now is writing registration. Code example:
with this I get exception
TypeError: Object of type ValueError is not JSON serializable
traceback:
I noticed I can raise
from ninja.errors import ValidationError
instead which works but it feels wrong since pydantic docs says "validators should either return the parsed value or raise a ValueError or AssertionError (assert statements may be used)." . Also this leads to errors having a different structure than the other pydantic errors. https://docs.pydantic.dev/2.1/usage/validators/#field-validatorsHow do I do this in the right way? Thanks!
The text was updated successfully, but these errors were encountered: