'Python [Pydantic] - default values based other object in the class
I would like to set default email in case email not provided, i.e:
name = a
last_name = b
email = None
email will become "[email protected]"
I tried something like that but obviously didn't work as the name,last_name not define in function.
class User(BaseModel):
name: Optional[str] = ''
last_name: Optional[str] = ''
email: EmailStr
@validator('email')
def set_email(cls, email):
if not email:
return name + last_name + '@email.com'
else:
return email
Update- still not working, i tried:
@root_validator(pre=True)
def email_set_config(cls, values):
email, name ,last_name = values.get('email'), values.get('name') , values.get('last_name')
if email is None :
email= name + '_' + name + '@' + last_name
return values
Solution:
added to Class :
--Update
- Actually it is working without define class Config:
class Config:
validate_assignment = True
@validator('email')
def set_email(cls, v, values, **kwargs):
return email or values["name"] + '_' + values["name"] + '@' + ["last_name"]
Solution 1:[1]
Not enough reputation to comment, so I'll just expand on @NobbyNobbs answer here^^
For your last example using pydantic.validator
, you can use the always
kwargs to always run a validator even if the value is not provided.
Link in the doc: https://pydantic-docs.helpmanual.io/usage/validators/#validate-always
So your example would be:
from typing import Optional
import pydantic
class User(pydantic.BaseModel):
first_name: str
last_name: str
email: Optional[pydantic.EmailStr]
@pydantic.validator('email', always=True)
def set_email(cls, v, values, **kwargs):
return v or values["first_name"] + '_' + values["last_name"] + '@email.com'
if __name__ == "__main__":
print(User(first_name="jon", last_name="doe")) # first_name='jon' last_name='doe' email='[email protected]'
print(User(first_name="jon", last_name="doe", email=None)) # first_name='jon' last_name='doe' email='[email protected]'
Solution 2:[2]
I changed your model a bit, now it has two required fields and one optional, which calculated from the others two.
from typing import Optional
import pydantic
class User(pydantic.BaseModel):
first_name: str
last_name: str
email: Optional[pydantic.EmailStr]
First approach to validate your data during instance creation, and have full model context at the same time, is using the @pydantic.root_validator
:
@pydantic.root_validator
def email_set_config(cls, values):
email, name, last_name = values.get('email'), values.get('first_name'), values.get('last_name')
if email is None:
values["email"] = name + '_' + last_name + '@email.com'
return values
if __name__ == "__main__":
u = User(first_name="jon", last_name="doe")
print(u) # first_name='jon' last_name='doe' email='[email protected]'
But if you want to control instantiation process only, I suggest you just override __init__
dunder in your model like this
def __init__(__pydantic_self__, **data: Any) -> None:
email, name, last_name = data.get('email'), data.get('first_name'), data.get('last_name')
if email is None:
data["email"] = name + '_' + last_name + '@email.com'
super().__init__(**data)
It's more simple and intuitive approach comparing to validator on my sight, and I would prefer it.
The possible solution with non-root validator, you mention in question, has tricky and a bit unexpected behavior for me.
It works like expected if you pass named email
argument to constructor, but doesn't if you not.
@pydantic.validator('email')
def set_email(cls, v, values, **kwargs):
return v or values["first_name"] + '_' + values["last_name"] + '@email.com'
if __name__ == "__main__":
print(User(first_name="jon", last_name="doe")) # first_name='jon' last_name='doe' email=None
print(User(first_name="jon", last_name="doe", email=None)) # first_name='jon' last_name='doe' email='[email protected]'
Solution 3:[3]
In my case the solution was:
@pydantic.root_validator(pre=True)
def method_before_validation(cls, data):
I used It in the root class of a complex object. I have smth like object with orders and separate object with client and I needed to put client's id to each order's object. So we can make data modification with data structure, types e.t.c on fly before the actual validation.
Solution 4:[4]
Pydantic has added default_factory
since v1.5, you can use the below code to have the default EmailStr value, as well as the other submodel class object
class User(BaseModel):
name: Optional[str] = ''
last_name: Optional[str] = ''
email: EmailStr = Field(
default_factory=lambda: EmailStr('[email protected]'))
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Dharman |
Solution 3 | Yunnosch |
Solution 4 |