¿Puedo hacer un valor predeterminado en Pydantic si se pasa Ninguno en el campo?
Tengo el siguiente código, pero me parece que el validador aquí solo funciona en la inicialización del modelo y no de otra manera.
Mi código:
class User(BaseModel): name: Optional[str] = '' password: Optional[str] = '' email: EmailStr @validator('name') def set_name(cls, name): return name or 'foo'Problema encontrado:
user = User(name=None, password='some_password', email='user@example.com') print("Name is ", user.name) # > 'Name is foo' user.name = None print("Name is ", user.name) # > 'Name is None'Salida deseada:
user = User(name='some_name', password='some_password', email='user@example.com') user.name = None print("Name is ", user.name) # > 'Name is foo'¿Alguna idea sobre cómo puedo obtener el resultado deseado? Creo que tener getters y setters ayudará a abordar el problema. Sin embargo, no pude hacer que funcionaran en un modelo Pydantic:
Intentando implementar getters y 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='user@example.com') # > RecursionError: maximum recursion depth exceededTambién probé el decorador de propiedades:
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='user@example.com') user.password = 'some_password' # > ValueError: "User" object has no field "password"También intenté sobrescribir el 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='user@example.com') 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 eitherConsidere agregar un método __init__ a su clase. Algo como
def __init__(self, name, pass, email): if pass is None: self.password = "foo" else: self.password = pass self.name = name self.email = emailDebe habilitar la opción de validate_assignment en la configuración del modelo:
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 fooEsta pregunta se hizo perfectamente, así que quería dar un ejemplo más amplio, porque hay muchas formas de asignar un valor dinámicamente.
La respuesta de Alex es correcta, pero solo funciona cuando el campo hereda directamente una clase de datos, más específicamente, algo como esto no funcionará.
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'Por razones de rendimiento, de forma predeterminada, no se llama a los validadores para los campos cuando no se proporciona un valor. Pero en situaciones como esta, cuando necesita establecer un valor predeterminado dinámico , podemos establecerlo en 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'Pero hay un problema importante con always, ya que estamos usando always=True pydantic intentaría validar el valor predeterminado None, lo que provocaría un error.
Al configurar Pre en True , llamará a ese campo antes de que ocurra un error de validación, el valor predeterminado de un validador pre se establece en False , en cuyo caso se llaman después de la validación del campo.
Pero esto tiene algunas desventajas.
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' Cuando lo establece en Ninguno, devuelve el valor dinámico correctamente, pero en algunas situaciones, como si fuera completamente None , falla.
In: user = User() Out: name=''Una vez más, debe configurar para que funcione.
pre=True always=Truedefault_factory Esto es principalmente útil en los casos en que desea establecer un valor predeterminado, como UUID o fecha y hora, etc. En esos casos, es posible que desee usar default_factory , pero hay un problema importante: no puede asignar un argumento Callable a 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)Muchas formas de asignar un valor predeterminado
Método #1: Un campo de id requerido con valor predeterminado
class User(BaseModel): id: str = uuid.uuid4() Método #2 Un campo de id opcional con valor predeterminado
class User(BaseModel): id: Optional[str] = uuid.uuid4() Método #3: Un campo de id requerido con valor predeterminado
class User(BaseModel): id: str = Field(default=uuid.uuid4()) Método n.º 4: un campo de id obligatorio con un valor predeterminado de invocable. Esto es útil para generar valores a pedido, como unique UUIDs o marcas de Timestamps . Vea la respuesta de @yagiz-degirmenci.
class User(BaseModel): id: str = Field(default_factory=uuid.uuid4) # uuid.uuid4 is not executed immediately