Search code examples
pythonfastapipydantic

How to pass the default value to a variable if None was passed?


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

Solution

  • 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