Can I make a default value in Pydantic if None is passed in the field?
I have the following code, but it seems to me that the validator here only works on initialization of the model and not otherwise.
My Code:
class User(BaseModel):
name: Optional[str] = ''
password: Optional[str] = ''
email: EmailStr
@validator('name')
def set_name(cls, name):
return name or 'foo'
Problem Encountered:
user = User(name=None, password='some_password', email='[email protected]')
print("Name is ", user.name)
# > 'Name is foo'
user.name = None
print("Name is ", user.name)
# > 'Name is None'
Desired Output:
user = User(name='some_name', password='some_password', email='[email protected]')
user.name = None
print("Name is ", user.name)
# > 'Name is foo'
Any ideas on how I can obtain the desired output? I think having getters and setters will help in tackling the issue. However, I could not get them to work in a Pydantic model:
Attempting to implement getters and setters:
class User(BaseModel):
name: Optional[str] = ''
password: Optional[str] = ''
email: EmailStr
def get_password(self):
return self.password
def set_password(self, password):
self.password = hash_password(password)
password = property(get_password, set_password)
user = User(name='some_name', password='some_password', email='[email protected]')
# > RecursionError: maximum recursion depth exceeded
I also tried the property decorator:
class User(BaseModel):
name: Optional[str] = ''
password: Optional[str] = ''
email: EmailStr
@property
def password(self):
return self._password
@password.setter
def password(self, password):
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
self._password = pwd_context.hash(password)
user = User(name='some_name', email='[email protected]')
user.password = 'some_password'
# > ValueError: "User" object has no field "password"
I also tried overwriting the init:
class User(BaseModel):
name: Optional[str] = ""
password: Optional[str] = ""
email: EmailStr
def __init__(self, name, password, email):
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
password = pwd_context.hash(password)
super().__init__(name=name, password=password, email=email)
user = User(name="some_name", password="some_password", email='[email protected]')
print(user.password)
# > AYylwSnbQgCHrl4uue6kO7yiuT20lazSzK7x # Works as expected
user.password = "some_other_password"
print(user.password)
# > "some_other_password" # Does not work
user.password = None
print(user.password)
# > None # Does not work either
You can set the default values for variables by adding ! default flag to the end of the variable value. It will not re-assign the value, if it is already assigned to the variable.
The root type can be any type supported by pydantic, and is specified by the type hint on the __root__ field. The root value can be passed to the model __init__ via the __root__ keyword argument, or as the first and only argument to parse_obj . Python 3.7 and above Python 3.9 and above.
Pydantic has been a game-changer in defining and using data types. It makes the code way more readable and robust while feeling like a natural extension to the language. It is an easy-to-use tool that helps developers validate and parse data based on given definitions, all fully integrated with Python's type hints.
You need to enable validate_assignment
option in model config:
from typing import Optional
from pydantic import BaseModel, validator
class User(BaseModel):
name: Optional[str] = ''
password: Optional[str] = ''
class Config:
validate_assignment = True
@validator('name')
def set_name(cls, name):
return name or 'foo'
user = User(name=None, password='some_password', )
print("Name is ", user.name)
user.name = None
print("Name is ", user.name)
Name is foo
Name is foo
This question asked perfectly so i wanted to provide a wider example, because there are many ways to assign a value dynamically.
Alex's answer is correct but it only works on when the Field directly inherits a dataclass more specifically something like this won't work.
class User(BaseModel):
name: Optional[str] = ""
password: Optional[str] = ""
class Config:
validate_assignment = True
@validator("name")
def set_name(cls, name):
return name or "bar"
user_dict = {"password": "so_secret"}
user_one = User(**user_dict)
Out: name='' password='so_secret'
For performance reasons, by default validators are not called for fields when a value is not supplied. But situations like this when you need to set a Dynamic Default Value we can set that to True
class User(BaseModel):
name: Optional[str] = ""
@validator("name", pre=True, always=True)
def set_name(cls, name):
return name or "bar"
In: user_one = User(name=None)
In: user_two = User()
Out: name='bar'
Out: name='bar'
But there is a one important catch with always, since we are using always=True pydantic would try to validate the default None which would cause an error.
Setting Pre to True
it will call that field before validation error occurs, the default of a validator pre is set to False
, in which case they're called after field validation.
But this has some disadvantages.
class User(BaseModel):
name: Optional[str] = ""
class Config:
validate_assignment = True
@validator("name")
def set_name(cls, name):
return name or "foo"
In: user = User(name=None)
Out: name='foo'
When you set it to None it returns the dynamic value correctly but some situations like it is completely None
, it fails.
In: user = User()
Out: name=''
Again you need to set, to make that work.
pre=True
always=True
default_factory
This is mostly useful in cases when you want to set a default value, like UUID or datetime etc. In that cases you might want to use default_factory
, but there is a big catch you can't assign a Callable
argument to the default_factory.
class User(BaseModel):
created_at: datetime = Field(default_factory=datetime.now)
In: user = User()
Out: created_at=datetime.datetime(2020, 8, 29, 2, 40, 12, 780986)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With