La validación de datos en FastAPI va mucho más allá de comprobar tipos básicos como string o entero. Con Pydantic puedes verificar formatos específicos como un email válido y, además, consultar la base de datos para asegurar que un valor sea único. Esto es clave para cualquier desarrollador backend que quiera mantener la integridad de los datos según la lógica de negocio.
¿Cómo validar un email con EmailStr en Pydantic?
Pydantic incluye tipos predefinidos que se encargan de validaciones comunes sin que tengas que escribir lógica adicional. Uno de los más útiles es EmailStr, que comprueba que el valor tenga formato de correo electrónico, con arroba y dominio.
Para usarlo, abres tu archivo models.py y añades el import desde Pydantic. Luego, en tu modelo Customer, cambias el tipo del campo email de str a EmailStr. Con ese pequeño ajuste, FastAPI rechazará cualquier email mal formado y devolverá un ValueError indicando que el valor no es válido.
¿Qué hace EmailStr en Pydantic? Valida automáticamente que un campo tenga formato de email: contenga arroba y un dominio al final. Si el valor no cumple, Pydantic lanza un error antes de que el dato llegue a tu lógica.
Si pruebas el endpoint de crear customers con un email inválido, recibirás un mensaje claro en body.email. Al pasar uno válido, la creación funciona sin problemas [01:00].
¿Cómo crear validaciones personalizadas con field_validator?
Validar el formato no siempre alcanza. En muchos casos necesitas reglas que dependen de tu base de datos, como evitar emails duplicados. Aquí entra el decorador field_validator de Pydantic v2.
En versiones anteriores se usaba validator, pero ese decorador está deprecado. La documentación de FastAPI marca esa línea como obsoleta y sugiere migrar a field_validator, que es la forma actual de declarar validaciones personalizadas en Pydantic 2 [01:55].
¿Cómo se estructura un field_validator?
Dentro del modelo creas un método, por ejemplo validate_email, y lo decoras con @field_validator("email") indicando el campo o campos a validar. La función recibe dos argumentos: cls y value, y debe retornar el valor ya validado.
Un detalle importante: field_validator solo se aplica a métodos de instancia tratados como class methods. Por eso, en lugar de self usas cls y agregas el decorador @classmethod encima. Sin ese ajuste, la aplicación lanza un error y no arranca.
python
from pydantic import EmailStr, field_validator
from sqlmodel import Session, select
from db import engine
class Customer(SQLModel):
email: EmailStr
@field_validator("email")@classmethod
def validate_email(cls, value): session =Session(engine) query =select(Customer).where(Customer.email== value) result = session.exec(query).first()ifresult: raise ValueError("this email is already registered")return value
¿Cómo validar que un email sea único contra la base de datos?
La lógica dentro del validador conecta tu modelo con la base de datos. Para eso necesitas tres piezas: la clase Session desde sqlmodel, el engine ya creado en tu archivo db, y la función select para construir la consulta.
La consulta es directa: seleccionas de la tabla Customer con una cláusula where que compara el campo email con el valor recibido. Ejecutas la query con session.exec(query).first(), que devuelve el primer resultado o None si no hay coincidencias.
Si result tiene un valor, significa que ya existe un usuario con ese correo y lanzas raise ValueError("this email is already registered").
Si result es None, retornas value y la creación continúa con normalidad.
Esta misma estructura sirve para validar cualquier campo contra cualquier tabla, no solo emails.
¿Cuándo usar field_validator en lugar de un constraint unique? Úsalo cuando necesites lógica personalizada o validar contra varias tablas. Para unicidad simple, un unique=True en el campo es más eficiente, pero field_validator te da control total sobre el mensaje y el flujo.
Al probar el endpoint con un correo ya registrado, FastAPI responde con un error en body.email indicando que el email está registrado. Esa respuesta se construye automáticamente a partir del ValueError que lanzaste dentro del validador [04:30].
¿Por qué importa validar más allá de los tipos?
Validar tipos protege tu API de inputs malformados, pero la integridad real de tus datos depende de reglas de negocio. Un email único, un rango de fechas coherente o un identificador que exista en otra tabla son validaciones que solo tú puedes definir.
Con EmailStr cubres el formato. Con field_validator y una sesión activa cubres cualquier regla que requiera consultar tu base de datos. La combinación te permite construir modelos que rechazan datos inválidos antes de que toquen tu capa de servicio.
Ahora te toca a ti: agrega más validadores en tus modelos, prueba con otros campos como teléfono o documento de identidad, y comparte tus implementaciones en los comentarios.
La validación de datos en FastAPI va mucho más allá de comprobar tipos básicos como string o entero. Con Pydantic puedes verificar formatos específicos como un email válido y, además, consultar la base de datos para asegurar que un valor sea único. Esto es clave para cualquier desarrollador backend que quiera mantener la integridad de los datos según la lógica de negocio.
¿Cómo validar un email con EmailStr en Pydantic?
Pydantic incluye tipos predefinidos que se encargan de validaciones comunes sin que tengas que escribir lógica adicional. Uno de los más útiles es EmailStr, que comprueba que el valor tenga formato de correo electrónico, con arroba y dominio.
Para usarlo, abres tu archivo models.py y añades el import desde Pydantic. Luego, en tu modelo Customer, cambias el tipo del campo email de str a EmailStr. Con ese pequeño ajuste, FastAPI rechazará cualquier email mal formado y devolverá un ValueError indicando que el valor no es válido.
¿Qué hace EmailStr en Pydantic? Valida automáticamente que un campo tenga formato de email: contenga arroba y un dominio al final. Si el valor no cumple, Pydantic lanza un error antes de que el dato llegue a tu lógica.
Si pruebas el endpoint de crear customers con un email inválido, recibirás un mensaje claro en body.email. Al pasar uno válido, la creación funciona sin problemas [01:00].
¿Cómo crear validaciones personalizadas con field_validator?
Validar el formato no siempre alcanza. En muchos casos necesitas reglas que dependen de tu base de datos, como evitar emails duplicados. Aquí entra el decorador field_validator de Pydantic v2.
En versiones anteriores se usaba validator, pero ese decorador está deprecado. La documentación de FastAPI marca esa línea como obsoleta y sugiere migrar a field_validator, que es la forma actual de declarar validaciones personalizadas en Pydantic 2 [01:55].
¿Cómo se estructura un field_validator?
Dentro del modelo creas un método, por ejemplo validate_email, y lo decoras con @field_validator("email") indicando el campo o campos a validar. La función recibe dos argumentos: cls y value, y debe retornar el valor ya validado.
Un detalle importante: field_validator solo se aplica a métodos de instancia tratados como class methods. Por eso, en lugar de self usas cls y agregas el decorador @classmethod encima. Sin ese ajuste, la aplicación lanza un error y no arranca.
python
from pydantic import EmailStr, field_validator
from sqlmodel import Session, select
from db import engine
class Customer(SQLModel):
email: EmailStr
@field_validator("email")
@classmethod
def validate_email(cls, value):
session = Session(engine)
query = select(Customer).where(Customer.email == value)
result = session.exec(query).first()
if result:
raise ValueError("this email is already registered")
return value
¿Cómo validar que un email sea único contra la base de datos?
La lógica dentro del validador conecta tu modelo con la base de datos. Para eso necesitas tres piezas: la clase Session desde sqlmodel, el engine ya creado en tu archivo db, y la función select para construir la consulta.
La consulta es directa: seleccionas de la tabla Customer con una cláusula where que compara el campo email con el valor recibido. Ejecutas la query con session.exec(query).first(), que devuelve el primer resultado o None si no hay coincidencias.
Si result tiene un valor, significa que ya existe un usuario con ese correo y lanzas raise ValueError("this email is already registered").
Si result es None, retornas value y la creación continúa con normalidad.
Esta misma estructura sirve para validar cualquier campo contra cualquier tabla, no solo emails.
¿Cuándo usar field_validator en lugar de un constraint unique? Úsalo cuando necesites lógica personalizada o validar contra varias tablas. Para unicidad simple, un unique=True en el campo es más eficiente, pero field_validator te da control total sobre el mensaje y el flujo.
Al probar el endpoint con un correo ya registrado, FastAPI responde con un error en body.email indicando que el email está registrado. Esa respuesta se construye automáticamente a partir del ValueError que lanzaste dentro del validador [04:30].
¿Por qué importa validar más allá de los tipos?
Validar tipos protege tu API de inputs malformados, pero la integridad real de tus datos depende de reglas de negocio. Un email único, un rango de fechas coherente o un identificador que exista en otra tabla son validaciones que solo tú puedes definir.
Con EmailStr cubres el formato. Con field_validator y una sesión activa cubres cualquier regla que requiera consultar tu base de datos. La combinación te permite construir modelos que rechazan datos inválidos antes de que toquen tu capa de servicio.
Ahora te toca a ti: agrega más validadores en tus modelos, prueba con otros campos como teléfono o documento de identidad, y comparte tus implementaciones en los comentarios.
Creo que validar el email en la base de datos se debería hacer en una capa de servicio a la base de datos.
Haciéndolo en models.py queda innecesariamente acoplado y mala práctica para una arquitectura limpia, aunque se haya hecho solo con fines didácticos
Un método con el decorador @classmethod puede acceder a la clase misma a través de su primer argumento, típicamente llamado cls, lo que permite interactuar y modificar el estado de la clase. Sin este decorador, un método es un método de instancia y solo puede acceder a los atributos de una instancia específica de la clase. Esto es útil para crear métodos que actúan a nivel de clase, como métodos de fábrica. En resumen, permite trabajar con la clase, mientras que un método normal trabaja solo con instancias.
Podemos agregar una validación para que la edad sea mayor o igual a 18 usando el parámetro (ge - greater than or equal)
age: int = Field(default=None, ge=18, description="La edad debe ser mayor o igual a 18 años")
```En la documentación oficial pueden ver más parámetros como:
* `le` - less than or equal to
* `multiple_of` - a multiple of the given number
Fields - Pydantic
Qué pasa en la actualización de un cliente? Supongamos que queremos modificar uno de los datos del cliente (por ejemplo la edad), pero no el email, cuando vayamos a guardar la información, la validación de la existencia del email en la bd no arrojará el error?
La validación de datos en FastAPI se realiza utilizando Pydantic, lo que permite definir reglas de validación de manera declarativa y eficaz. A continuación, exploraremos cómo implementar esta funcionalidad en FastAPI.
1. Definición de Modelos de Pydantic
En FastAPI, los modelos de Pydantic son usados para validar datos de entrada y salida. Puedes establecer tipos de datos, valores predeterminados, restricciones y más.
from pydantic import BaseModel, Field, EmailStr
class User(BaseModel):
username: str = Field(..., min_length=3, max_length=50, description="Nombre del usuario")
email: EmailStr
age: int = Field(..., gt=0, le=120, description="Edad del usuario, debe ser entre 1 y 120 años")
2. Validación Automática en Endpoints
Cuando defines un endpoint con un modelo de Pydantic como parámetro, FastAPI valida automáticamente los datos recibidos.
Si envías datos que no cumplen con las reglas definidas (por ejemplo, un correo no válido o un nombre muy corto), FastAPI responderá con un error 422 y detalles sobre la validación.
3. Uso de Validaciones Personalizadas
Puedes definir validaciones adicionales utilizando métodos en el modelo de Pydantic.
Ejemplo: Validar Nombre Único
from pydantic import BaseModel, validator
class User(BaseModel):
username: str
email: EmailStr
age: int
@validator("username")
def validate_unique_username(cls, value):
if value.lower() in ["admin", "root"]:
raise ValueError("El nombre de usuario no puede ser 'admin' o 'root'")
return value
4. Validación Compleja con Dependencias
Además de usar Pydantic, puedes implementar dependencias para manejar validaciones más dinámicas.
from fastapi import Depends
def validate_age(age: int):
if age < 18:
raise HTTPException(status_code=400, detail="La edad debe ser mayor o igual a 18")
return age
@app.get("/validate/")
async def check_age(age: int = Depends(validate_age)):
return {"message": f"La edad {age} es válida"}
5. Validación en la Salida
Puedes usar modelos de Pydantic para controlar cómo se devuelven los datos al cliente.
from fastapi.responses import JSONResponse
class UserOut(BaseModel):
username: str
email: EmailStr
classCustomerBase(SQLModel):name: str =Field(default=None)description: str |None=Field(default=None)email:EmailStr=Field(default=None, unique=True)age: int =Field(default=None)
customers.py
@router.post("/customers", response_model=Customer)async def create_customer(customer_data:CustomerCreate,session:SessionDep): customer =Customer.model_validate(customer_data.model_dump())try: session.add(customer) session.commit() session.refresh(customer)return customer
except IntegrityError: session.rollback() # Rollback the transaction to keep the session clean
raise HTTPException( status_code=400, detail="A customer with this email already exists.")
Creo que validar el email en la base de datos se debería hacer en una capa de servicio a la base de datos.
Haciéndolo en models.py queda innecesariamente acoplado y mala práctica para una arquitectura limpia, aunque se haya hecho solo con fines didácticos
No siento que sea así. En este caso aún cuando en diseño de Arquitectura si queda acoplado, siento que precisamente Pydantic ofrece estas opciones para crear código fácil de leer.
Y al menos en mi filosofía, prefiero código fácil de leer que código que cumpla con todos los estándares de cálidad.
Un método con el decorador @classmethod puede acceder a la clase misma a través de su primer argumento, típicamente llamado cls, lo que permite interactuar y modificar el estado de la clase. Sin este decorador, un método es un método de instancia y solo puede acceder a los atributos de una instancia específica de la clase. Esto es útil para crear métodos que actúan a nivel de clase, como métodos de fábrica. En resumen, @classmethod permite trabajar con la clase, mientras que un método normal trabaja solo con instancias.
Podemos agregar una validación para que la edad sea mayor o igual a 18 usando el parámetro (ge - greater than or equal)
age: int =Field(default=None, ge=18, description="La edad debe ser mayor o igual a 18 años")```En la documentación oficial pueden ver más parámetros como:
* `le` - less than or equal to
* `multiple_of` - a multiple of the given number
Fields-Pydantic
si están usando UV deben instalarlo usando
uv add "pydantic[email]"
Qué pasa en la actualización de un cliente? Supongamos que queremos modificar uno de los datos del cliente (por ejemplo la edad), pero no el email, cuando vayamos a guardar la información, la validación de la existencia del email en la bd no arrojará el error?
Hola Luis, sí te podría arrojar ese error, para evitarlo podrías o no mandar el email para actualizar el cliente, o cambiar la lógica del model: En SQL se puede colocar que un campo sea único, "unique", agregas el campo "email" como "unique", y a la hora de agregar un Customer, y si el correo ya existe en algún Customer, te va retornar un error, pero el caso de la actualización solo te retorna un error si el nuevo email que intentas agregar a ese Customer ya existe en otro Customer.
profe que increible curso lo quiero mucho
La validación de datos en FastAPI se realiza utilizando Pydantic, lo que permite definir reglas de validación de manera declarativa y eficaz. A continuación, exploraremos cómo implementar esta funcionalidad en FastAPI.
1. Definición de Modelos de Pydantic
En FastAPI, los modelos de Pydantic son usados para validar datos de entrada y salida. Puedes establecer tipos de datos, valores predeterminados, restricciones y más.
from pydantic import BaseModel, Field, EmailStr
class User(BaseModel):
username: str = Field(..., min_length=3, max_length=50, description="Nombre del usuario")
email: EmailStr
age: int = Field(..., gt=0, le=120, description="Edad del usuario, debe ser entre 1 y 120 años")
2. Validación Automática en Endpoints
Cuando defines un endpoint con un modelo de Pydantic como parámetro, FastAPI valida automáticamente los datos recibidos.
Si envías datos que no cumplen con las reglas definidas (por ejemplo, un correo no válido o un nombre muy corto), FastAPI responderá con un error 422 y detalles sobre la validación.
3. Uso de Validaciones Personalizadas
Puedes definir validaciones adicionales utilizando métodos en el modelo de Pydantic.
Ejemplo: Validar Nombre Único
from pydantic import BaseModel, validator
class User(BaseModel):
username: str
email: EmailStr
age: int
@validator("username")
def validate_unique_username(cls, value):
if value.lower() in ["admin", "root"]:
raise ValueError("El nombre de usuario no puede ser 'admin' o 'root'")
return value
4. Validación Compleja con Dependencias
Además de usar Pydantic, puedes implementar dependencias para manejar validaciones más dinámicas.
from fastapi import Depends
def validate_age(age: int):
if age < 18:
raise HTTPException(status_code=400, detail="La edad debe ser mayor o igual a 18")
return age
@app.get("/validate/")
async def check_age(age: int = Depends(validate_age)):
return {"message": f"La edad {age} es válida"}
5. Validación en la Salida
Puedes usar modelos de Pydantic para controlar cómo se devuelven los datos al cliente.
from fastapi.responses import JSONResponse
class UserOut(BaseModel):
username: str
email: EmailStr
@app.get("/users/{user_id}", response_model=UserOut)
async def get_user(user_id: int):
user = {"username": "john_doe", "email": "john.doe@example.com", "age": 30} # Simulación
return user
En este caso, solo se devuelven username y email, aunque el objeto user contiene más datos.
6. Validación de Entradas y Salidas Juntas
Puedes usar modelos diferentes para entrada y salida si los datos requeridos son distintos.
class UserIn(BaseModel):
username: str
password: str
class UserOut(BaseModel):
username: str
email: EmailStr
sentia que debia aplciar lo aprendido anteriormente investigue y asi es un forma pero usted es ekq ue sabe
En el caso propuesto, cuál es la necesidad de usar @classmethod?
Yo lo he omitido y no encuentro falla
Mira este ejemplo con los tres tipo de methods que hay en python.
En un caso es self, en otro es cls, y en otro incluso no tiene param.
classCalculadora: factor =10# Atributo de clasedef__init__(self, nombre): self.nombre = nombre # Atributo de instancia# 1. MÉTODO DE INSTANCIAdefsumar(self, a, b):print(f"{self.nombre} suma: {a} + {b}")return a + b
# 2. MÉTODO DE CLASE@classmethoddefmultiplicar_por_factor(cls, numero):print(f"Usando factor de clase: {cls.factor}")return numero * cls.factor
# 3. MÉTODO ESTÁTICO@staticmethoddefdividir(a, b):print("División sin acceso a clase ni instancia")return a / b
# USO:calc = Calculadora("MiCalc")# Método de instancia (necesita self)calc.sumar(5,3)# MiCalc suma: 5 + 3# Método de clase (puede llamarse desde clase o instancia)Calculadora.multiplicar_por_factor(5)# Usando factor de clase: 10calc.multiplicar_por_factor(5)
agregué un método que limpia los espacios al principio o al final de name y description:
classCustomerCreate(CustomerBase):# aquí se crea el id"""modelo de datos del cliente""" name :str= Field(min_length=2, max_length=60, title="Client Name") description: Optional[str]= Field(default=None, max_length=500) email : EmailStr = Field() age :int= Field(gt=17, title="The client must be of legal age (18 years or older)")@field_validator('name','description')@classmethoddefclean_strings(class_, value:str)->str:if value isnotNone:return value.strip()return value
Corrección
El decorador de Validator está depreciado para Pydantic 2.0