Reputation: 23
Let's say I have a pydantic model with an optional field:
class MyModel(BaseModel):
field_1: str | None
That I instantiate by passing a dict using parse_obj()
Now, I would like the pydantic object to be None
if none of the field members were set.
Example:
data_a = {
'field_1': 'value_1'
}
obj_a = MyModel.parse_obj(data_a)
print(type(obj_a)) # <class 'MyModel'>
data_b = {
'foo': 'bar'
}
obj_b = MyModel.parse_obj(data_b)
print(type(obj_b)) # I would like this to be NoneType !
Of course I know I could check if the fields exist in the input data before making any instantiation, but I want to avoid that and make it in a more generic way (imagine like having many different models with different fields).
Upvotes: 2
Views: 2421
Reputation: 12008
Unfortunately for you, pydantic's root validation will either raise an error or instantiate the class. A different approach is required for your desired outcome. A solution here is to override the BaseClass
's __new__
operator (which invokes the class to create a new instance) – to not instantiate if conditions aren't met:
from typing import Optional
from pydantic import BaseModel
class MyBaseModel(BaseModel):
"""
Pydantic baseclass with overloaded operator for
instantiating new objects.
"""
def __new__(cls, *args, **kwargs):
"""Be careful when using this method:
https://docs.python.org/3/reference/datamodel.html#object.__new__
"""
# If all args are none -> do nothing
if all(v is None for v in args) and all(v is None for v in kwargs.values()):
pass
else:
return super().__new__(cls)
class MyModel(MyBaseModel):
field_1: Optional[str]
field_2: Optional[str]
data_a = {'field_1': 'foo', 'field_2': None}
data_b = {'field_1': None, 'field_2': None}
obj_a = MyModel.parse_obj(data_a)
print(type(obj_a)) # <class 'MyModel'>
obj_b = MyModel.parse_obj(data_b)
print(type(obj_b)) # <class 'NoneType'>
Upvotes: 1
Reputation: 3489
You could use all()
and a list comprehension:
if all(val is None for val in dict(obj_b).values()):
obj_b = None
Or, alternatively, if none of the fields will purposefully be set to None
, you could check if any of the fields have been set:
if not obj_b.__fields_set__:
obj_b = None
Both of these could be compressed:
# First
obj_b = None if all(val is None for val in dict(obj_b).values()) else obj_b
# Second
obj_b = obj_b if obj_b.__fields_set__ else None
Here's a base class that does this automatically:
class NoneCheckModel(BaseModel):
"""Model with None checking"""
@classmethod
def parse_obj(*args, **kwargs):
result = super().parse_obj(*args, **kwargs)
return None if all(val is None for val in dict(result).values()) else result
Upvotes: 3