Nathan
Nathan

Reputation: 43

Pydantic dataclass handle ValidationError

I am looking for solution to handle ValidationError when passing data into pydantic dataclass.

from pydantic import BaseModel

class TestB(BaseModel):
    id: str
    price : float
    quantity : int

class TestA(BaseModel):
    orderId: str
    totalAmountPaid: float
    products: List[TestB]

data_correct_type = {"orderId": "12341234", "totalAmountPaid": 395.5,
              "products": [{"id": "abcd0001", "price": '299', "quantity": 1},
                           {"id": "abcd0002", "price": '199', "quantity": 1}]}

data_wrong_type = {"orderId": "12341234", "totalAmountPaid": 395.5,
              "products": [{"id": "abcd0001", "price": '299', "quantity": 1},
                           {"id": "abcd0002", "price": 'abc', "quantity": 1}]}

result_pydantic_1 = TestA(**data_correct_type) #works
result_pydantic_2 = TestA(**data_wrong_type) 

output:
raise 1 validation error for TestA
products -> 1 -> price
  value is not a valid float (type=type_error.float)

Any ways I can replace the field with wrong type with some thing like None value? Thanks!

Upvotes: 1

Views: 2726

Answers (2)

s.tafazzoli
s.tafazzoli

Reputation: 81

In Pydantic V2, you can achieve this using Annotated and WrapValidator. The WrapValidator is applied around the Pydantic inner validation logic. So, in the validate_value function below, if the inner validation fails, the function handles the exception and returns None as the default value.

import logging
from typing import Any, Annotated

from pydantic import BaseModel, ValidationError, WrapValidator, ValidatorFunctionWrapHandler


def validate_value(v: Any, handler: ValidatorFunctionWrapHandler):
    try:
        return handler(v)
    except ValidationError:
        logging.warning('Value %s has invalid type.', v)
        return None


class TestB(BaseModel):
    price: Annotated[float, WrapValidator(validate_value)] = None

Upvotes: 1

rzlvmp
rzlvmp

Reputation: 9364

Code:

from pydantic import BaseModel, validator
from typing import Union

class P(BaseModel):
    string: str
    number: Union[float, None]

    # pre=True → run before standard validators
    @validator('number', pre=True)
    def set_to_none_if_not_a_numeric(cls, v):
        try:
            return float(v)
        except ValueError:
            print('Not a number!')
            return None

a = P(**{"string": "11", "number": "12aa"})
b = P(**{"string": "11", "number": "12"})

print(a)
print('----')
print(b)

Output:

Not a number!
string='11' number=None
----
string='11' number=12.0

Answering to

what if I want to do it with all the fields?

I recommend to check pydantic docs at first. It is very detailed and have many good examples

Example regarding your question:

class DemoModel(BaseModel):
    square_numbers: List[int] = []
    cube_numbers: List[int] = []

    # '*' is the same as 'cube_numbers', 'square_numbers' here:
    @validator('*', pre=True)
    def split_str(cls, v):
        if isinstance(v, str):
            return v.split('|')
        return v

    @validator('cube_numbers', 'square_numbers')
    def check_sum(cls, v):
        if sum(v) > 42:
            raise ValueError('sum of numbers greater than 42')
        return v

So you can use @validator('field1', 'field2') or @validator('*')

Answering to

However what I want to achieve is for all the field in the dataclass, it will try to convert to the desired type as defined in the dataclass

Just use your imagination:

Code:

from pydantic import BaseModel, validator
from typing import Optional


class P(BaseModel):
    string_or_none: Optional[str]
    float_or_none: Optional[float]
    int_or_none: Optional[int]
    bool_or_none: Optional[bool]

    @validator('*', pre=True)
    def cast_var_types(cls, value, field):

        allowed_type = field.type_
        try:
            print(f'Try cast {value} to', str(allowed_type))
            return allowed_type(value)
        except ValueError:
            return None


good_values = P(**{"string_or_none": 1234321, "float_or_none": "12.111", "int_or_none": "987", "bool_or_none": 1})
bad_values = P(**{"string_or_none": "this will be good anyway", "float_or_none": "abc", "int_or_none": "bac", "bool_or_none": "any value can be casted to bool"})

print()
print('  ====================================')
print()

for k in ["string_or_none", "float_or_none", "int_or_none", "bool_or_none"]:
    print(k, "of good values is:")
    print('type →', type(getattr(good_values, k)))
    print('value →', getattr(good_values, k))
    print('----------------')
    print(k, "of bad values is:")
    print('type →', type(getattr(bad_values, k)))
    print('value →', getattr(bad_values, k))
    print('----------------')

And output:

Try cast 1234321 to <class 'str'>
Try cast 12.111 to <class 'float'>
Try cast 987 to <class 'int'>
Try cast 1 to <class 'bool'>
Try cast this will be good anyway to <class 'str'>
Try cast abc to <class 'float'>
Try cast bac to <class 'int'>
Try cast any value can be casted to bool to <class 'bool'>

  ====================================

string_or_none of good values is:
type → <class 'str'>
value → 1234321
----------------
string_or_none of bad values is:
type → <class 'str'>
value → this will be good anyway
----------------
float_or_none of good values is:
type → <class 'float'>
value → 12.111
----------------
float_or_none of bad values is:
type → <class 'NoneType'>
value → None
----------------
int_or_none of good values is:
type → <class 'int'>
value → 987
----------------
int_or_none of bad values is:
type → <class 'NoneType'>
value → None
----------------
bool_or_none of good values is:
type → <class 'bool'>
value → True
----------------
bool_or_none of bad values is:
type → <class 'bool'>
value → True
----------------

Upvotes: 2

Related Questions