79682262

Date: 2025-06-27 17:01:47
Score: 0.5
Natty:
Report link

You can raise PydanticCustomError from pydantic_core, instead of ValueError.

Your Pydantic Model will be something like this:

from datetime import date
from typing import Optional

from pydantic import (
    BaseModel,
    field_validator,
    HttpUrl,
    EmailStr
)
from pydantic import ValidationError
from pydantic_core import PydanticCustomError


class Company(BaseModel):
    company_id: Optional[int] = None
    company_name: Optional[str]
    address: Optional[str]
    state: Optional[str]
    country: Optional[str]
    postal_code: Optional[str]
    phone_number: Optional[str]
    email: Optional[EmailStr] = None
    website_url: Optional[HttpUrl] = None
    cin: Optional[str]
    gst_in: Optional[str] = None
    incorporation_date: Optional[date]
    reporting_currency: Optional[str]
    fy_start_date: Optional[date]
    logo: Optional[str] = None

    @field_validator('company_name')
    def validate_company_name(cls, v):
        if v is None or not v.strip():
            raise PydanticCustomError(
                'value_error', # This will be the "type" field
                'Company name must be provided.', # This will be the "msg" field
            )
        return v

If you want a more sophisticated solution, you can view more about this discussion on Pydantic Repository. But basically you can create a WrapperClass to use with Annoted type from typing module.

I am gonne give my example because have also the ValidationInfo parameter in the validation field method

import inspect
from pydantic import (
    ValidationInfo,
    ValidatorFunctionWrapHandler,
    WrapValidator,
)
from pydantic_core import PydanticCustomError


class APIFriendlyErrorMessages:
    """
    A WrapValidator that catches ValueError and AssertionError exceptions and
    raises a PydanticCustomError with the message from the original exception,
    while removing the error type prefix, which is not user-friendly.
    """

    def __new__(cls, validator: Callable[[Any], None]) -> WrapValidator:
        """
        Wrap a validator function with a WrapValidator that catches ValueError and
        AssertionError exceptions and raises a PydanticCustomError with the message
        from the original exception, while removing the error type prefix, which is
        not user-friendly.

        :param validator: The validator function to wrap.
        :returns: A WrapValidator instance that prettifies error messages.
        """
        # I added this, in the discussion he used just with "v" value
        signature = inspect.signature(validator)
        
        # Verify if the validate function has validation info parameter
        has_validation_info = any(
            param.annotation == ValidationInfo
            for _, param in signature.parameters.items()
        )

        def _validator(
            v: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
        ):
            try:
                # If not have validation info, call just with v
                if not has_validation_info:
                    validator(v)
                else:
               # Or Else call with v and info
                    validator(v, info)
            except ValueError as exc:
                # This is the same Pydantic Custom Error we used before
                raise PydanticCustomError(
                    'value_error',
                    str(exc),
                )

            return handler(v)

        return WrapValidator(_validator)

And in my model:

from datetime import datetime
from decimal import Decimal
from typing import Annotated, Optional

from pydantic import BaseModel, Field, ValidationInfo, field_validator
from app.api.transactions.enums import PaymentMethod, TransactionType
from app.utils.schemas import APIFriendlyErrorMessages # Importing my Custom Wrapper


# Validate Function
def validate_total_installments(value: int, info: ValidationInfo) -> int:
    if value > 1 and info.data['method'] != PaymentMethod.CREDIT_CARD:
        # Raising ValueError
        raise ValueError('Pagamentos a vista não podem ser parcelados.')

    return value


# Annoted Type using the Wrapper and the validate Function
TotalInstallments = Annotated[int, APIFriendlyErrorMessages(validate_total_installments)]


class TransactionIn(BaseModel):
    total: Decimal = Field(ge=Decimal('0.01'))
    description: Optional[str] = None
    type: TransactionType
    method: PaymentMethod
    total_installments: TotalInstallments = Field(ge=1, default=1) # Using your annoted type here
    executed_at: datetime
    bank_account_id: int
    category_id: Optional[int] = None

I expect that help you.

Reasons:
  • Blacklisted phrase (1): não
  • Long answer (-1):
  • Has code block (-0.5):
  • Low reputation (1):
Posted by: Davi Gomes Lucciola