How to prevent Pydantic from throwing an exception on ValidationError

Viewed 8072

How to prevent Pydantic from throwing an exception on ValidationError?

from pydantic import BaseModel, NonNegativeInt


class Person(BaseModel):
    name: str
    age: NonNegativeInt
    details: None


p1: Person = Person(name='Alice', age=30, details=None)
print(p1)
p2: Person = Person(name='Bob', age=40, details={'height': 1.70, 'weight': 91})
print(p1)

I want this exception to NOT kill the program and log a warning instead

name='Alice' age=30
Traceback (most recent call last):
  File "playground/test_pydantic_prevent_exception.py", line 12, in <module>
    p2: Person = Person(name='Bob', age=40, details={'height': 1.70, 'weight': 91})
  File "pydantic/main.py", line 406, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for Person
details
  value is not None (type=type_error.not_none)

The use case that explains why I need this is as follows:

I am working on a product that is not 100% complete, so the returned values might change overnight from returning None to some other object {} which is unknown while I develop on my side.

When I query the product and get an object instead of None, the assignment can fail, but I want to log a warning, allow the program to continue, because this is not a blocker in many cases.

3 Answers

You can use pydantic Optional to keep that None

Example:

from pydantic.schema import Optional, Dict
from pydantic import BaseModel, NonNegativeInt

class Person(BaseModel):
    name: str
    age: NonNegativeInt
    details: Optional[Dict]

This will allow to set null value.

This is a complete script with a new class BaseModelNoException that inherits Pydantic's BaseModel, wraps the exception ValidationError and throws a warning instead.


import json
from traceback import TracebackException
from typing import no_type_check, Type, Any

from pydantic import BaseModel, NonNegativeInt, ValidationError, StrBytes, Protocol


class BaseModelNoException(BaseModel):
    def __init__(__pydantic_self__, **data: Any) -> None:
        try:
            super(BaseModelNoException, __pydantic_self__).__init__(**data)
        except ValidationError as pve:
            print(f'This is a warning. __init__ failed to validate:\n {json.dumps(data, indent=4)}\n')
            print(f'This is the original exception:\n{pve.json()}')

    @no_type_check
    def __setattr__(self, name, value):
        try:
            return super(BaseModelNoException, self).__setattr__
        except ValidationError as pve:
            print(f'This is a warning. __setattr__ failed to validate:\n {json.dumps({name: value}, indent=4)}')
            print(f'This is the original exception:\n{pve.json()}')
            return None

    @classmethod
    def parse_obj(cls: Type['Model'], obj: Any) -> 'Model':
        try:
            return super(BaseModelNoException, cls).parse_obj(obj)
        except ValidationError as pve:
            print(f'This is a warning. parse_obj failed to validate:\n {json.dumps(obj, indent=4)}')
            print(f'This is the original exception:\n{pve.json()}')
            return None

    @classmethod
    def parse_raw(cls: Type['Model'], b: StrBytes, *, content_type: str = None, encoding: str = 'utf8',
                  proto: Protocol = None, allow_pickle: bool = False, ) -> 'Model':
        try:
            return super(BaseModelNoException, cls).parse_raw(b=b, content_type=content_type, encoding=encoding,
                                                              proto=proto, allow_pickle=allow_pickle)
        except ValidationError as pve:
            print(f'This is a warning. parse_raw failed to validate:\n {b}')
            print(f'This is the original exception:\n{pve.json()}')
            return None


# class Person(BaseModel):
class PersonDetails(BaseModel):
    height: NonNegativeInt
    weight: NonNegativeInt


class Person(BaseModelNoException):
    name: str = ''
    age: NonNegativeInt = 0
    details: PersonDetails


p1: Person = Person(name='Alice', age=30, details=None)
print(p1)

print('-' * 100)

p2: Person = Person(name='Bob', age=40, details={'height': 1.70, 'weight': 91})
print(p2)

print('-' * 100)

p3: Person = Person.parse_obj({'name': 'Alice', 'age': 31, 'details': {'height': 1.70, 'weight': -77}})
print(p3)

print('-' * 100)

print('The End')

This is the output of the above script.

/playground/test_pydantic_prevent_exception.py This is a warning. init failed to validate: { "name": "Alice", "age": 30, "details": null }

This is the original exception:
[
  {
    "loc": [
      "details"
    ],
    "msg": "none is not an allowed value",
    "type": "type_error.none.not_allowed"
  }
]

----------------------------------------------------------------------------------------------------
name='Bob' age=40 details=PersonDetails(height=1, weight=91)
----------------------------------------------------------------------------------------------------
This is a warning. __init__ failed to validate:
 {
    "name": "Alice",
    "age": 31,
    "details": {
        "height": 1.7,
        "weight": -77
    }
}

This is the original exception:
[
  {
    "loc": [
      "details",
      "weight"
    ],
    "msg": "ensure this value is greater than or equal to 0",
    "type": "value_error.number.not_ge",
    "ctx": {
      "limit_value": 0
    }
  }
]

----------------------------------------------------------------------------------------------------
The End

Process finished with exit code 0

Can be used @validator("age", pre=True)

from pydantic import BaseModel, validator


class Person(BaseModel):
    name: str
    age: int
    details: None

    @validator("age", pre=True)
    def parse_age(cls, value):
        if isinstance(value, str):
            print('Here is the error text or logging')
            return 1
        return value
Related