Reputation: 43
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
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
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
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('*')
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