Pydantic es la biblioteca de validación de datos más popular de Python, que puede convertir sugerencias de tipo en reglas de validación en tiempo de ejecución.
En lugar de escribir docenas de si isinstance() y funciones de validación personalizadas, define tu estructura de datos una sola vez utilizando la conocida sintaxis de Python.
Pydantic se encarga del resto: valida los datos entrantes, convierte los tipos cuando es necesario y proporciona mensajes de error claros cuando falla la validación.
Considera esta sencilla función de Python con sugerencias de tipo y la documentación adecuada:
defcalculate_user_discount(age:int, is_premium_member:bool, purchase_amount:float)->float:"""Calculate discount percentage based on user profile and purchase amount."""if age >=65:
base_discount =0.15elif is_premium_member:
base_discount =0.10else:
base_discount =0.05return purchase_amount * base_discountCon la tecnología de
Si observas su firma, sabrás exactamente qué tipo de datos acepta y produce en el otro extremo. Sin embargo, ¿puedes decir lo mismo del usuario final? Al fin y al cabo, estas indicaciones de tipo sólo sirven para documentar: si alguien pasa un tipo de dato incorrecto, el intérprete de Python nunca se molesta:
# This runs without error, even though types are completely wrong!
discount = calculate_user_discount(True,1,5)print(discount)# Output: 0.5 (True becomes 1, 1 is truthy, so 5 * 0.10)Con la tecnología de
Este es un ejemplo clásico de la tipificación dinámica de Python. Ofrece a los programadores un desarrollo rápido y libertad, pero a costa de introducir problemas de validación de tipos que a menudo surgen en producción.
En este tutorial, te explicaré cómo utilizar Pydantic para aplicaciones del mundo real. Empezaremos con tu primer modelo de datos y avanzaremos a través de patrones avanzados utilizados en sistemas de producción. Descubrirás cómo validar estructuras de datos anidadas, escribir validadores personalizados para reglas de negocio complejas e integrar Pydantic con marcos populares como FastAPI y SQLAlchemy.
¿Qué es Pydantic?
Básicamente estás jugando a la ruleta de los datos cuando creas aplicaciones que interactúan con el mundo exterior. Los usuarios pueden enviar un número de teléfono como una cadena en lugar de un número entero, una API puede devolver valores nulos donde esperas números, o un archivo de configuración puede contener errores tipográficos que rompan tu aplicación a las 3 de la madrugada.
Los enfoques tradicionales de Python a este problema se vuelven rápidamente difíciles de manejar. Acabas escribiendo un código de validación como éste:
defcreate_user(data):# Manual validation nightmareifnotisinstance(data.get('age'),int):raise ValueError("Age must be an integer")if data['age']<0or data['age']>150:raise ValueError("Age must be between 0 and 150")ifnotisinstance(data.get('email'),str)or'@'notin data['email']:raise ValueError("Invalid email format")ifnotisinstance(data.get('is_active'),bool):raise ValueError("is_active must be a boolean")# Finally create the user...return User(data['age'], data['email'], data['is_active'])Con la tecnología de
Multiplica esto por cada estructura de datos de tu aplicación, y pasarás más tiempo escribiendo código de validación que lógica empresarial.
Pydantic lo resuelve combinando tres potentes conceptos: sugerencias de tipo, validación en tiempo de ejecución y serialización automática. En lugar de comprobaciones manuales, defines tu estructura de datos una vez utilizando la sintaxis de anotación de tipos de Python, y Pydantic se encarga de toda la validación automáticamente:
from pydantic import BaseModel, EmailStr
from typing import Optional
classUser(BaseModel):
age:int
email: EmailStr
is_active:bool=True
nickname: Optional[str]=None# Pydantic automatically validates and converts data
user_data ={"age":"25",# String gets converted to int"email":"[email protected]","is_active":"true"# String gets converted to bool}
user = User(**user_data)print(user.age)# 25 (as integer)print(user.model_dump())# Clean dictionary outputCon la tecnología de
Características de Pydantic
El enfoque de Pydantic te ofrece varias ventajas. El rendimiento es la primera ventaja que notarás: la lógica central de validación de Pydantic está escrita en Rust, lo que la hace más rápida que la validación escrita a mano en Python en la mayoría de los casos. Tu aplicación puede procesar miles de peticiones sin que la validación se convierta en un cuello de botella.
Pydantic también permite la integración con frameworks modernos. FastAPI, uno de los frameworks web de Python de más rápido crecimiento, utiliza modelos Pydantic para generar automáticamente la documentación de la API, validar los cuerpos de las peticiones y serializar las respuestas. Cuando defines un modelo Pydantic, obtienes gratis la generación de esquemas OpenAPI:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()classUserCreate(BaseModel):
name:str
email: EmailStr
age:int@app.post("/users/")asyncdefcreate_user(user: UserCreate):# FastAPI automatically validates the request body# and generates API docs from your Pydantic modelreturn{"message":f"Created user {user.name}"}Con la tecnología de
La generación del esquema JSON se produce automáticamente con cada modelo Pydantic. Esto significa que tus estructuras de datos se autodocumentan, y puedes generar bibliotecas de clientes, reglas de validación para aplicaciones frontales o esquemas de bases de datos a partir de la misma fuente de verdad.
Pydantic contra Clases de datos vs. Malvavisco
Pero, ¿cuándo debes elegir Pydantic frente a otras alternativas? Si estás comparando Pydantic frente a clases de datos, la decisión se reduce a las necesidades de validación. @dataclass de Python es perfecto para contenedores de datos sencillos en los que confías en la entrada, pero Pydantic sobresale cuando necesitas validación, serialización e integración con marcos web:
from dataclasses import dataclass
from pydantic import BaseModel
# Dataclass: fast, simple, no validation@dataclassclassUserDataclass:
name:str
age:int# Pydantic: validation, serialization, framework integrationclassUserPydantic(BaseModel):
name:str
age:intCon la tecnología de
Al comparar Pydantic con Marshmallow, el enfoque de sugerencias de tipo de Pydantic resulta más natural para los programadores de Python, mientras que Marshmallow utiliza clases de esquema. Pydantic también ofrece un mejor rendimiento y una integración más sencilla con los marcos asíncronos modernos.
Pydantic funciona mejor cuando estás construyendo APIs, procesando datos externos, gestionando la configuración, o cualquier escenario en el que un fallo en la validación de datos deba detectarse pronto en lugar de causar misteriosos errores más tarde. Convierte los errores de ejecución en mensajes de validación claros y procesables que ayudan tanto a programadores como a usuarios a entender qué ha fallado.
Primeros pasos con Pydantic
Vamos a sumergirnos en cómo utilizar Pydantic en tus proyectos, desde la instalación hasta la creación de tus primeros modelos de datos.
Instalación y configuración
Configurar Pydantic es sencillo, pero algunos errores comunes pueden hacer tropezar a los recién llegados. Te guiaré por el proceso de instalación adecuado y te ayudaré a evitar los errores que provocan depuraciones innecesarias.
Primero, crea un entorno virtual para mantener aisladas las dependencias de tu proyecto. Así se evitan conflictos de versión que pueden causar misteriosos errores de importación más adelante:
# Create and activate virtual environment (works in zsh and bash)
python -m venv pydantic_env
source pydantic_env/bin/activate # On macOS/Linux# pydantic_env\Scripts\activate # On WindowsCon la tecnología de
Para la funcionalidad de validación del correo electrónico, que probablemente necesitarás en aplicaciones reales (y en este tutorial), instala los extras de correo electrónico:
Las comillas alrededor de pydantic[email] son importantes en casi todos los terminales. Sin ellos, es posible que aparezca un error "no se han encontrado coincidencias".
Nota: Si te encuentras con ModuleNotFoundError: No module named 'pydantic', comprueba estas cuestiones:
Entorno de Python incorrecto: Asegúrate de que tu entorno virtual está activado
Múltiples versiones de Python: Comprueba que utilizas la misma versión de Python que instaló Pydantic
Configuración IDE: Puede que tu editor de código esté utilizando un intérprete de Python diferente
Advertencia de denominación crítica: Nunca nombres tu archivo Python pydantic.py. Esto crea una importación circular que romperá tu código con mensajes de error confusos. Python intentará importar tu archivo en lugar de la biblioteca Pydantic real.
Tu primer modelo pydántico
Construyamos un modelo de usuario sencillo para ver Pydantic en acción. En lugar de empezar con ejemplos abstractos, resolveremos un problema real: validar los datos de registro de un usuario desde un formulario web.
from pydantic import BaseModel, EmailStr
from typing import Optional
from datetime import datetime
classUser(BaseModel):
name:str
email: EmailStr
age:int
is_active:bool=True
created_at: datetime =None# Test with clean data
clean_data ={"name":"Alice Johnson","email":"[email protected]","age":28}
user = User(**clean_data)print(f"User created: {user.name}, Age: {user.age}")print(f"Model output: {user.model_dump()}")Con la tecnología de
Salida:
User created: Alice Johnson, Age: 28
Model output: {'name': 'Alice Johnson', 'email': '[email protected]', 'age': 28, 'is_active': True, 'created_at': None}Con la tecnología de
Vamos a desglosar lo que ocurre en esta definición del modelo:
Crear un modelo pydántico: Tu clase User hereda de BaseModel, lo que le proporciona todas las capacidades de validación y serialización de Pydantic. Esta herencia convierte una clase normal de Python en una herramienta de validación de datos.
Definiciones de los campos: Cada línea de la clase define un campo con su tipo previsto. La sintaxisname: str indica a Pydantic que name debe ser una cadena, age: int significa que la edad debe ser un número entero, y así sucesivamente.
EmailStr explicado: EmailStr es un tipo especial de Pydantic que valida automáticamente las direcciones de correo electrónico. Proviene del paquete pydantic[email] que instalaste anteriormente y utiliza expresiones regulares para garantizar que el formato del correo electrónico es válido. Si alguien pasa "no-un-email", Pydantic emitirá un error de validación.
Valores por defecto: Los campos como is_active: bool = True tienen valores por defecto. Si no proporcionas estos campos al crear un usuario, Pydantic utiliza los predeterminados. En = None para created_at este campo es opcional.
Instanciación del modelo: Cuando llamas a User(**clean_data), ** desempaqueta tu diccionario y pasa cada par clave-valor como argumentos de palabra clave al constructor del modelo.
Veamos ahora en acción la conversión automática de tipos de Pydantic:
# Messy data that still works
messy_data ={"name":"Bob Smith","email":"[email protected]","age":"35",# String instead of int"is_active":"true"# String instead of bool}
user = User(**messy_data)print(f"Age type: {type(user.age)}")# <class 'int'>print(f"Is active type: {type(user.is_active)}")# <class 'bool'>Con la tecnología de
Veamos ahora en acción la conversión automática de tipos de Pydantic:
# Messy data that still works
messy_data ={"name":"Bob Smith","email":"[email protected]","age":"35",# String instead of int"is_active":"true"# String instead of bool}
user = User(**messy_data)print(f"Age type: {type(user.age)}")# <class 'int'>print(f"Is active type: {type(user.is_active)}")# <class 'bool'>Con la tecnología de
Como puedes ver, los camposage y is_inactive se convierten automáticamente a sus formatos adecuados mediante validación.
Cuando falla la validación, Pydantic proporciona mensajes de error claros:
from pydantic import ValidationError
try:
invalid_user = User(
name="",# Empty string
email="not-an-email",# Invalid email
age=-5# Negative age)except ValidationError as e:print(e)Con la tecnología de
Salida:
# Shows exactly which fields failed and why
1 validation error for User
email
value is not a valid email address: An email address must have an @-sign. [type=value_error, input_value='not-an-email', input_type=str]Con la tecnología de
BaseModel vs. clases de datos
Entender cuándo utilizar la BaseModel de Pydantic frente a la @dataclass de Python te ayuda a elegir la herramienta adecuada para cada situación.
Las clases de datos de Python son perfectas para contenedores de datos sencillos en los que tú controlas la entrada:
from dataclasses import dataclass
@dataclassclassProductDataclass:
name:str
price:float
in_stock:bool# Fast, simple, but no validation
product = ProductDataclass("Laptop",999.99,True)# This also works, even though types are wrong:
broken_product = ProductDataclass(123,"expensive","maybe")Con la tecnología de
Los modelos Pydantic añaden validación, serialización e integración de marcos:
from pydantic import BaseModel, Field
classProductPydantic(BaseModel):
name:str= Field(min_length=1)
price:float= Field(gt=0)# Must be greater than 0
in_stock:bool# Automatic validation prevents bad datatry:
product = ProductPydantic(name="", price=-10, in_stock="maybe")except ValidationError as e:print("Validation caught the errors!")# Valid data works perfectly
good_product = ProductPydantic(
name="Laptop",
price="999.99",# String converted to float
in_stock=True)Con la tecnología de
Cuándo elegir cada enfoque:
Utiliza clases de datos para estructuras de datos internas, objetos de configuración, o cuando el rendimiento sea crítico y confíes en tus fuentes de datos
Utiliza Pydantic para puntos finales de API, entradas de usuario, análisis de datos externos o cuando necesites serialización JSON
Pydantic añade cierta sobrecarga en comparación con las clases de datos, pero este coste suele ser insignificante comparado con los errores que evita y el tiempo de desarrollo que ahorra. Para las aplicaciones web, la integración automática con frameworks como FastAPI hace que Pydantic sea la elección clara.
Las funciones de validación y serialización adquieren más valor a medida que crece tu aplicación. Empezar con modelos Pydantic te proporciona una base sólida que se adapta a tus necesidades.
Construir modelos de datos con Pydantic
Ahora que ya conoces los conceptos básicos, abordemos los retos a los que te enfrentarás al crear aplicaciones listas para la producción.
Validación y limitaciones del campo
Considera una API de catálogo de productos en la que los datos de precios proceden de varios proveedores con diferentes normas de formato. Algunos envían los precios como cadenas, otros como flotantes y, ocasionalmente, alguien envía un precio negativo que bloquea tu sistema de facturación.
La funciónField() de Pydantic transforma las sugerencias de tipo básicas en sofisticadas reglas de validación que protegen tu aplicación:
from pydantic import BaseModel, Field
from decimal import Decimal
from typing import Optional
classProduct(BaseModel):
name:str= Field(min_length=1, max_length=100)
price: Decimal = Field(gt=0, le=10000)# Greater than 0, less than or equal to 10,000
description: Optional[str]= Field(None, max_length=500)
category:str= Field(..., pattern=r'^[A-Za-z\s]+$')# Only letters and spaces
stock_quantity:int= Field(ge=0)# Greater than or equal to 0
is_available:bool=True# This works - all constraints satisfied
valid_product = Product(
name="Wireless Headphones",
price="199.99",# String converted to Decimal
description="High-quality wireless headphones",
category="Electronics",
stock_quantity=50)# This fails with clear error messagestry:
invalid_product = Product(
name="",# Too short
price=-50,# Negative price
category="Electronics123",# Contains numbers
stock_quantity=-5# Negative stock)except ValidationError as e:print(f"Validation errors: {len(e.errors())} issues found")Con la tecnología de
Cada parámetro de Field() tiene una finalidad específica: min_length y max_length evitan las violaciones del esquema de la base de datos, gt y le crean límites de lógica empresarial, y pattern valida los datos formateados mediante expresiones regulares. La sintaxisField(...) con elipsis marca los campos obligatorios, mientras que Field(None, ...) crea campos opcionales con reglas de validación.
Coerción de tipos frente a validación estricta
Por defecto, Pydantic convierte los tipos compatibles en lugar de rechazarlos de plano. Esta flexibilidad funciona bien para las entradas del usuario, pero algunos escenarios exigen una correspondencia exacta de los tipos:
from pydantic import BaseModel, Field, ValidationError
# Default: lenient type coercionclassFlexibleOrder(BaseModel):
order_id:int
total_amount:float
is_paid:bool# These all work due to automatic conversion
flexible_order = FlexibleOrder(
order_id="12345",# String to int
total_amount="99.99",# String to float
is_paid="true"# String to bool)# Strict validation when precision mattersclassStrictOrder(BaseModel):
model_config ={"str_strip_whitespace":True,"validate_assignment":True}
order_id:int= Field(strict=True)
total_amount:float= Field(strict=True)
is_paid:bool= Field(strict=True)Con la tecnología de
El diccionario model_config controla el comportamiento de validación en todo tu modelo. La opción str_strip_whitespace limpia automáticamente la entrada de cadenas, mientras que validate_assignment garantiza que los cambios de campo posteriores a la creación del modelo sigan activando la validación. Los campos individuales pueden anular estos ajustes con Field(strict=True) para situaciones que requieran una correspondencia de tipo exacta, como los cálculos financieros o los datos científicos.
Modelos anidados y datos complejos
Las aplicaciones reales manejan estructuras de datos complejas e interconectadas. Un pedido de comercio electrónico contiene información del cliente, direcciones de envío y varios artículos de producto, cada uno de los cuales requiere su propia validación:
from typing import List
from datetime import datetime
classAddress(BaseModel):
street:str= Field(min_length=5)
city:str= Field(min_length=2)
postal_code:str= Field(pattern=r'^\d{5}(-\d{4})?$')
country:str="USA"classCustomer(BaseModel):
name:str= Field(min_length=1)
email: EmailStr
shipping_address: Address
billing_address: Optional[Address]=NoneclassOrderItem(BaseModel):
product_id:int= Field(gt=0)
quantity:int= Field(gt=0, le=100)
unit_price: Decimal = Field(gt=0)classOrder(BaseModel):
order_id:str= Field(pattern=r'^ORD-\d{6}$')
customer: Customer
items: List[OrderItem]= Field(min_items=1)
order_date: datetime = Field(default_factory=datetime.now)# Complex nested data validation
order_data ={"order_id":"ORD-123456","customer":{"name":"John Doe","email":"[email protected]","shipping_address":{"street":"123 Main Street","city":"Anytown","postal_code":"12345"}},"items":[{"product_id":1,"quantity":2,"unit_price":"29.99"},{"product_id":2,"quantity":1,"unit_price":"149.99"}]}
order = Order(**order_data)print(f"Order validated with {len(order.items)} items")Con la tecnología de
Pydantic valida las estructuras anidadas de forma recursiva: el campo Customer se convierte en un objeto completo Customer, que valida su propio campo Address. La sintaxis List[OrderItem] valida cada elemento de la lista como un OrderItem, mientras que Field(min_items=1) impide que los pedidos vacíos lleguen a tu sistema de inventario. Utilizando default_factory=datetime.now se crean marcas de tiempo únicas para cada instancia de pedido.
Campos opcionales y manejo de Ninguno
Diferentes operaciones necesitan diferentes requisitos de datos. La creación de usuarios exige información completa, mientras que las actualizaciones deben aceptar cambios parciales:
from typing import Optional
classUserCreate(BaseModel):
name:str= Field(min_length=1)
email: EmailStr
age:int= Field(ge=13, le=120)
phone: Optional[str]= Field(None, pattern=r'^\+?1?\d{9,15}$')classUserUpdate(BaseModel):
name: Optional[str]= Field(None, min_length=1)
email: Optional[EmailStr]=None
age: Optional[int]= Field(None, ge=13, le=120)
phone: Optional[str]= Field(None, pattern=r'^\+?1?\d{9,15}$')# PATCH request with partial data
update_data ={"name":"Jane Smith","age":30}
user_update = UserUpdate(**update_data)# Serialize only provided fields
patch_data = user_update.model_dump(exclude_none=True)print(f"Fields to update: {list(patch_data.keys())}")Con la tecnología de
Serialización convierte los objetos Pydantic en diccionarios o cadenas JSON para su almacenamiento o transmisión. El método model_dump() se encarga de esta conversión, y exclude_none=True elimina los campos no proporcionados. Este patrón funciona perfectamente para las peticiones PATCH, en las que los clientes envían sólo los campos que quieren cambiar, evitando sobrescrituras accidentales de datos en tu base de datos.
Esta base te prepara para el siguiente reto: implementar una lógica de validación personalizada que capture las reglas empresariales exclusivas de tu aplicación.
Validación personalizada e integración en el mundo real
Construir aplicaciones de producción significa manejar datos que no se ajustan a los patrones estándar de comprobación de tipos.
Piensa en un formulario de registro de usuario en el que los requisitos de contraseña varían en función de los planes de suscripción, o en una API que recibe datos de direcciones de varios países con distintos formatos de código postal. Estos escenarios requieren una lógica de validación personalizada que capture tus reglas empresariales específicas y, al mismo tiempo, se integre sin problemas con los marcos web y los sistemas de configuración.
Esta sección te muestra cómo implementar prácticos patrones de validación personalizados, integrar modelos Pydantic con FastAPI para una documentación automática de la API, y gestionar la configuración de la aplicación mediante variables de entorno utilizando el enfoque del archivo.env en el que se basan la mayoría de las aplicaciones de producción.
Validadores de campo y validación de modelos
Cuando la lógica empresarial determina la validez de los datos, el decorador @field_validator de Pydantic transforma tus funciones de validación en parte del propio modelo. Considera un sistema de registro de usuarios en el que los distintos niveles de suscripción tienen distintos requisitos de contraseña:
from pydantic import BaseModel, field_validator, Field
import re
classUserRegistration(BaseModel):
username:str= Field(min_length=3)
email: EmailStr
password:str
subscription_tier:str= Field(pattern=r'^(free|pro|enterprise)$')@field_validator('password')@classmethoddefvalidate_password_complexity(cls, password, info):
tier = info.data.get('subscription_tier','free')iflen(password)<8:raise ValueError('Password must be at least 8 characters')if tier =='enterprise'andnot re.search(r'[A-Z]', password):raise ValueError('Enterprise accounts require uppercase letters')return passwordCon la tecnología de
El decorador @field_validator te da acceso a otros valores de campo a través del parámetro info.data, lo que permite reglas de validación que dependen de varios campos. El validador se ejecuta después de pasar la comprobación básica de tipos, por lo que puedes suponer con seguridad que subscription_tier es uno de los valores permitidos.
Para la validación que abarca varios campos, el decorador@model_validator se ejecuta después de validar todos los campos individuales:
from datetime import datetime
from pydantic import model_validator
classEventRegistration(BaseModel):
start_date: datetime
end_date: datetime
max_attendees:int= Field(gt=0)
current_attendees:int= Field(ge=0)@model_validator(mode='after')defvalidate_event_constraints(self):if self.end_date <= self.start_date:raise ValueError('Event end date must be after start date')if self.current_attendees > self.max_attendees:raise ValueError('Current attendees cannot exceed maximum')return selfCon la tecnología de
El parámetro mode='after' garantiza que el validador reciba una instancia del modelo totalmente construida, lo que lo hace perfecto para la lógica empresarial que requiere acceder a todos los campos validados. El validador debe devolver self para indicar que la validación se ha realizado correctamente.
Integración FastAPI
La integración de FastAPI con Pydantic crea la validación automática de peticiones y la documentación de la API. El patrón clave consiste en crear modelos separados para las distintas operaciones, lo que te permite controlar qué datos fluyen en cada dirección:
from fastapi import FastAPI
from typing import Optional
from datetime import datetime
app = FastAPI()classUserCreate(BaseModel):
username:str= Field(min_length=3)
email: EmailStr
password:str= Field(min_length=8)classUserResponse(BaseModel):id:int
username:str
email: EmailStr
created_at: datetime
@app.post("/users/", response_model=UserResponse)asyncdefcreate_user(user: UserCreate):# FastAPI automatically validates the request body
new_user ={"id":1,"username": user.username,"email": user.email,"created_at": datetime.now()}return UserResponse(**new_user)Con la tecnología de
La separación entre modelos de entrada y de salida ofrece varias ventajas. Los modelos de entrada pueden incluir reglas de validación y campos obligatorios, mientras que los modelos de salida controlan exactamente qué datos se envían a los clientes. FastAPI genera automáticamente documentación OpenAPI a partir de tus modelos Pydantic, creando documentos de API interactivos que los programadores pueden utilizar para probar los puntos finales.
Para las operaciones de actualización, puedes crear modelos en los que todos los campos sean opcionales:
classUserUpdate(BaseModel):
username: Optional[str]= Field(None, min_length=3)
email: Optional[EmailStr]=None@app.patch("/users/{user_id}")asyncdefupdate_user(user_id:int, user_update: UserUpdate):# Only update provided fields
update_data = user_update.model_dump(exclude_unset=True)# Your database update logic herereturn{"message":f"Updated user {user_id}"}Con la tecnología de
El parámetro exclude_unset=True en las operaciones PATCH garantiza que sólo actualizas los campos que se proporcionaron explícitamente, evitando sobrescrituras accidentales. Este patrón funciona perfectamente para las API REST en las que los clientes envían actualizaciones parciales.
Gestión de la configuración con variables de entorno
Las aplicaciones de producción necesitan una gestión de la configuración segura y fácil de implantar. La páginaBaseSettings de Pydantic, combinada con los archivos.env, proporciona una configuración segura que funciona en entornos de desarrollo, preparación y producción.
Primero, crea un archivo.env en la raíz de tu proyecto:
# .env fileDATABASE_URL=postgresql://user:password@localhost:5432/myapp
SECRET_KEY=your-secret-key-here
DEBUG=false
ALLOWED_HOSTS=localhost,127.0.0.1,yourdomain.comCon la tecnología de
A continuación, define tu modelo de configuración:
from pydantic import BaseSettings, Field
from typing import List
classAppSettings(BaseSettings):
database_url:str= Field(description="Database connection URL")
secret_key:str= Field(description="Secret key for JWT tokens")
debug:bool= Field(default=False)
allowed_hosts: List[str]= Field(default=["localhost"])classConfig:
env_file =".env"
case_sensitive =False# Load settings automatically from environment and .env file
settings = AppSettings()Con la tecnología de
La clase BaseSettings lee automáticamente las variables de entorno, los archivos .env y los argumentos de la línea de comandos. Las variables de entorno tienen prioridad sobre los valores del archivo .env, lo que facilita anular la configuración en distintos entornos de despliegue. La opcióncase_sensitive = False permite nombrar las variables de entorno de forma flexible.
Para aplicaciones complejas, puedes organizar los ajustes en grupos lógicos:
Este enfoque anidado mantiene juntas las configuraciones relacionadas, al tiempo que mantiene una clara separación entre los distintos componentes de tu aplicación. Cada grupo de ajustes puede tener su propio prefijo de variable de entorno y sus propias reglas de validación.
El enfoque del archivo .env funciona con plataformas de despliegue como Heroku, AWS y Docker, donde las variables de entorno son la forma estándar de configurar las aplicaciones. Tu aplicación obtiene seguridad y validación de tipos mientras sigue los patrones de configuración nativos de la nube que esperan los equipos de operaciones.
Estos patrones constituyen la base para construir aplicaciones mantenibles que gestionen la complejidad del mundo real. El sistema de validación de Pydantic se adapta a tus requisitos específicos a la vez que proporciona mensajes de error claros y documentación automática que ayuda a todo tu equipo a comprender las estructuras de datos que espera tu aplicación.
Conclusión
Ya has visto cómo Pydantic puede ahorrarte el tedioso trabajo de escribir código de validación a mano. En lugar de saturar tus funciones con comprobaciones enisinstance() y un tratamiento de errores personalizado, puedes definir tu estructura de datos una vez y dejar que Pydantic se encargue del resto.
Asegúrate de leer la sección de Preguntas Frecuentes más abajo para preguntas más específicas sobre Pydantic. Para obtener documentación completa y patrones avanzados, aquí tienes algunos recursos que puedes utilizar:
¿La validación de Pydantic ralentiza mi aplicación?⌄
Pydantic V2 está construido con Rust bajo el capó, lo que lo hace muy rápido. Para la mayoría de las aplicaciones, la sobrecarga de validación es mínima en comparación con los errores que evita. Puedes utilizar model_construct() para omitir la validación de fuentes de datos de confianza cuando el rendimiento sea crítico.
¿Cuándo debo utilizar Pydantic en lugar de las clases de datos de Python?⌄
Utiliza Pydantic cuando necesites validación, trabajes con fuentes de datos externas o construyas API. Utiliza clases de datos para estructuras de datos internas sencillas en las que confíes en la entrada. Pydantic destaca cuando los datos proceden de usuarios, API o archivos de configuración.
¿Cómo manejo los errores de validación de Pydantic en las aplicaciones de producción?⌄
Captura las excepciones de ValidationError y extrae la lista .errors() para obtener información estructurada de los errores. Cada error contiene los campos loc, msg, y type que te ayudan a proporcionar mensajes de error fáciles de usar o a registrar información de depuración detallada.
¿Puedo añadir Pydantic al código existente sin romperlo todo?⌄
Sí. Empieza envolviendo los puntos de entrada de datos externos (peticiones API, análisis de archivos) con modelos Pydantic. Puedes convertir gradualmente las estructuras de datos internas. Pydantic funciona junto al código existente y no requiere cambiar toda tu base de código de una vez.
¿Cuál es el error más común al empezar con Pydantic?⌄
No entender el tipo de coacción. Pydantic convierte los tipos compatibles (como la cadena "123" al entero 123) por defecto. Si necesitas una comprobación de tipos estricta, utiliza Field(strict=True) o establece el modo estricto en la configuración de tu modelo para evitar las conversiones automáticas.
Aprende a trabajar con JSON en Python, incluyendo serialización, deserialización, formateo, optimización del rendimiento, manejo de API y comprensión de las limitaciones y alternativas de JSON.
Aprende sobre las clases abstractas de Python, su finalidad y cómo utilizar el módulo `abc` para aplicar interfaces coherentes. Incluye ejemplos prácticos y mejores prácticas para una implementación eficaz.
Pydantic es la biblioteca de validación de datos más popular de Python, que puede convertir sugerencias de tipo en reglas de validación en tiempo de ejecución.
En lugar de escribir docenas de si
isinstance()y funciones de validación personalizadas, define tu estructura de datos una sola vez utilizando la conocida sintaxis de Python.Pydantic se encarga del resto: valida los datos entrantes, convierte los tipos cuando es necesario y proporciona mensajes de error claros cuando falla la validación.
Considera esta sencilla función de Python con sugerencias de tipo y la documentación adecuada:
Si observas su firma, sabrás exactamente qué tipo de datos acepta y produce en el otro extremo. Sin embargo, ¿puedes decir lo mismo del usuario final? Al fin y al cabo, estas indicaciones de tipo sólo sirven para documentar: si alguien pasa un tipo de dato incorrecto, el intérprete de Python nunca se molesta:
Este es un ejemplo clásico de la tipificación dinámica de Python. Ofrece a los programadores un desarrollo rápido y libertad, pero a costa de introducir problemas de validación de tipos que a menudo surgen en producción.
En este tutorial, te explicaré cómo utilizar Pydantic para aplicaciones del mundo real. Empezaremos con tu primer modelo de datos y avanzaremos a través de patrones avanzados utilizados en sistemas de producción. Descubrirás cómo validar estructuras de datos anidadas, escribir validadores personalizados para reglas de negocio complejas e integrar Pydantic con marcos populares como FastAPI y SQLAlchemy.
¿Qué es Pydantic?
Básicamente estás jugando a la ruleta de los datos cuando creas aplicaciones que interactúan con el mundo exterior. Los usuarios pueden enviar un número de teléfono como una cadena en lugar de un número entero, una API puede devolver valores nulos donde esperas números, o un archivo de configuración puede contener errores tipográficos que rompan tu aplicación a las 3 de la madrugada.
Los enfoques tradicionales de Python a este problema se vuelven rápidamente difíciles de manejar. Acabas escribiendo un código de validación como éste:
Multiplica esto por cada estructura de datos de tu aplicación, y pasarás más tiempo escribiendo código de validación que lógica empresarial.
Pydantic lo resuelve combinando tres potentes conceptos: sugerencias de tipo, validación en tiempo de ejecución y serialización automática. En lugar de comprobaciones manuales, defines tu estructura de datos una vez utilizando la sintaxis de anotación de tipos de Python, y Pydantic se encarga de toda la validación automáticamente:
Características de Pydantic
El enfoque de Pydantic te ofrece varias ventajas. El rendimiento es la primera ventaja que notarás: la lógica central de validación de Pydantic está escrita en Rust, lo que la hace más rápida que la validación escrita a mano en Python en la mayoría de los casos. Tu aplicación puede procesar miles de peticiones sin que la validación se convierta en un cuello de botella.
Pydantic también permite la integración con frameworks modernos. FastAPI, uno de los frameworks web de Python de más rápido crecimiento, utiliza modelos Pydantic para generar automáticamente la documentación de la API, validar los cuerpos de las peticiones y serializar las respuestas. Cuando defines un modelo Pydantic, obtienes gratis la generación de esquemas OpenAPI:
La generación del esquema JSON se produce automáticamente con cada modelo Pydantic. Esto significa que tus estructuras de datos se autodocumentan, y puedes generar bibliotecas de clientes, reglas de validación para aplicaciones frontales o esquemas de bases de datos a partir de la misma fuente de verdad.
Pydantic contra Clases de datos vs. Malvavisco
Pero, ¿cuándo debes elegir Pydantic frente a otras alternativas? Si estás comparando Pydantic frente a clases de datos, la decisión se reduce a las necesidades de validación.
@dataclassde Python es perfecto para contenedores de datos sencillos en los que confías en la entrada, pero Pydantic sobresale cuando necesitas validación, serialización e integración con marcos web:Al comparar Pydantic con Marshmallow, el enfoque de sugerencias de tipo de Pydantic resulta más natural para los programadores de Python, mientras que Marshmallow utiliza clases de esquema. Pydantic también ofrece un mejor rendimiento y una integración más sencilla con los marcos asíncronos modernos.
Pydantic funciona mejor cuando estás construyendo APIs, procesando datos externos, gestionando la configuración, o cualquier escenario en el que un fallo en la validación de datos deba detectarse pronto en lugar de causar misteriosos errores más tarde. Convierte los errores de ejecución en mensajes de validación claros y procesables que ayudan tanto a programadores como a usuarios a entender qué ha fallado.
Primeros pasos con Pydantic
Vamos a sumergirnos en cómo utilizar Pydantic en tus proyectos, desde la instalación hasta la creación de tus primeros modelos de datos.
Instalación y configuración
Configurar Pydantic es sencillo, pero algunos errores comunes pueden hacer tropezar a los recién llegados. Te guiaré por el proceso de instalación adecuado y te ayudaré a evitar los errores que provocan depuraciones innecesarias.
Primero, crea un entorno virtual para mantener aisladas las dependencias de tu proyecto. Así se evitan conflictos de versión que pueden causar misteriosos errores de importación más adelante:
Ahora, instala Pydantic con el siguiente comando:
Para la funcionalidad de validación del correo electrónico, que probablemente necesitarás en aplicaciones reales (y en este tutorial), instala los extras de correo electrónico:
Las comillas alrededor de
pydantic[email]son importantes en casi todos los terminales. Sin ellos, es posible que aparezca un error "no se han encontrado coincidencias".Nota: Si te encuentras con
ModuleNotFoundError: No module named 'pydantic', comprueba estas cuestiones:Advertencia de denominación crítica: Nunca nombres tu archivo Python
pydantic.py. Esto crea una importación circular que romperá tu código con mensajes de error confusos. Python intentará importar tu archivo en lugar de la biblioteca Pydantic real.Tu primer modelo pydántico
Construyamos un modelo de usuario sencillo para ver Pydantic en acción. En lugar de empezar con ejemplos abstractos, resolveremos un problema real: validar los datos de registro de un usuario desde un formulario web.
Salida:
Vamos a desglosar lo que ocurre en esta definición del modelo:
Userhereda deBaseModel, lo que le proporciona todas las capacidades de validación y serialización de Pydantic. Esta herencia convierte una clase normal de Python en una herramienta de validación de datos.name: strindica a Pydantic quenamedebe ser una cadena,age: intsignifica que la edad debe ser un número entero, y así sucesivamente.EmailStres un tipo especial de Pydantic que valida automáticamente las direcciones de correo electrónico. Proviene del paquetepydantic[email]que instalaste anteriormente y utiliza expresiones regulares para garantizar que el formato del correo electrónico es válido. Si alguien pasa "no-un-email", Pydantic emitirá un error de validación.is_active: bool = Truetienen valores por defecto. Si no proporcionas estos campos al crear un usuario, Pydantic utiliza los predeterminados. En= Noneparacreated_ateste campo es opcional.User(**clean_data),**desempaqueta tu diccionario y pasa cada par clave-valor como argumentos de palabra clave al constructor del modelo.Veamos ahora en acción la conversión automática de tipos de Pydantic:
Veamos ahora en acción la conversión automática de tipos de Pydantic:
Salida:
Como puedes ver, los campos
ageyis_inactivese convierten automáticamente a sus formatos adecuados mediante validación.Cuando falla la validación, Pydantic proporciona mensajes de error claros:
Salida:
BaseModel vs. clases de datos
Entender cuándo utilizar la
BaseModelde Pydantic frente a la @dataclassde Python te ayuda a elegir la herramienta adecuada para cada situación.Las clases de datos de Python son perfectas para contenedores de datos sencillos en los que tú controlas la entrada:
Los modelos Pydantic añaden validación, serialización e integración de marcos:
Cuándo elegir cada enfoque:
Pydantic añade cierta sobrecarga en comparación con las clases de datos, pero este coste suele ser insignificante comparado con los errores que evita y el tiempo de desarrollo que ahorra. Para las aplicaciones web, la integración automática con frameworks como FastAPI hace que Pydantic sea la elección clara.
Las funciones de validación y serialización adquieren más valor a medida que crece tu aplicación. Empezar con modelos Pydantic te proporciona una base sólida que se adapta a tus necesidades.
Construir modelos de datos con Pydantic
Ahora que ya conoces los conceptos básicos, abordemos los retos a los que te enfrentarás al crear aplicaciones listas para la producción.
Validación y limitaciones del campo
Considera una API de catálogo de productos en la que los datos de precios proceden de varios proveedores con diferentes normas de formato. Algunos envían los precios como cadenas, otros como flotantes y, ocasionalmente, alguien envía un precio negativo que bloquea tu sistema de facturación.
La función
Field()de Pydantic transforma las sugerencias de tipo básicas en sofisticadas reglas de validación que protegen tu aplicación:Cada parámetro de
Field()tiene una finalidad específica:min_lengthymax_lengthevitan las violaciones del esquema de la base de datos,gtylecrean límites de lógica empresarial, ypatternvalida los datos formateados mediante expresiones regulares. La sintaxisField(...)con elipsis marca los campos obligatorios, mientras queField(None, ...)crea campos opcionales con reglas de validación.Coerción de tipos frente a validación estricta
Por defecto, Pydantic convierte los tipos compatibles en lugar de rechazarlos de plano. Esta flexibilidad funciona bien para las entradas del usuario, pero algunos escenarios exigen una correspondencia exacta de los tipos:
El diccionario
model_configcontrola el comportamiento de validación en todo tu modelo. La opciónstr_strip_whitespacelimpia automáticamente la entrada de cadenas, mientras quevalidate_assignmentgarantiza que los cambios de campo posteriores a la creación del modelo sigan activando la validación. Los campos individuales pueden anular estos ajustes conField(strict=True)para situaciones que requieran una correspondencia de tipo exacta, como los cálculos financieros o los datos científicos.Modelos anidados y datos complejos
Las aplicaciones reales manejan estructuras de datos complejas e interconectadas. Un pedido de comercio electrónico contiene información del cliente, direcciones de envío y varios artículos de producto, cada uno de los cuales requiere su propia validación:
Pydantic valida las estructuras anidadas de forma recursiva: el campo
Customerse convierte en un objeto completoCustomer, que valida su propio campoAddress. La sintaxisList[OrderItem]valida cada elemento de la lista como unOrderItem, mientras queField(min_items=1)impide que los pedidos vacíos lleguen a tu sistema de inventario. Utilizandodefault_factory=datetime.nowse crean marcas de tiempo únicas para cada instancia de pedido.Campos opcionales y manejo de Ninguno
Diferentes operaciones necesitan diferentes requisitos de datos. La creación de usuarios exige información completa, mientras que las actualizaciones deben aceptar cambios parciales:
Serialización convierte los objetos Pydantic en diccionarios o cadenas JSON para su almacenamiento o transmisión. El método
model_dump()se encarga de esta conversión, yexclude_none=Trueelimina los campos no proporcionados. Este patrón funciona perfectamente para las peticiones PATCH, en las que los clientes envían sólo los campos que quieren cambiar, evitando sobrescrituras accidentales de datos en tu base de datos.Esta base te prepara para el siguiente reto: implementar una lógica de validación personalizada que capture las reglas empresariales exclusivas de tu aplicación.
Validación personalizada e integración en el mundo real
Construir aplicaciones de producción significa manejar datos que no se ajustan a los patrones estándar de comprobación de tipos.
Piensa en un formulario de registro de usuario en el que los requisitos de contraseña varían en función de los planes de suscripción, o en una API que recibe datos de direcciones de varios países con distintos formatos de código postal. Estos escenarios requieren una lógica de validación personalizada que capture tus reglas empresariales específicas y, al mismo tiempo, se integre sin problemas con los marcos web y los sistemas de configuración.
Esta sección te muestra cómo implementar prácticos patrones de validación personalizados, integrar modelos Pydantic con FastAPI para una documentación automática de la API, y gestionar la configuración de la aplicación mediante variables de entorno utilizando el enfoque del archivo
.enven el que se basan la mayoría de las aplicaciones de producción.Validadores de campo y validación de modelos
Cuando la lógica empresarial determina la validez de los datos, el decorador
@field_validatorde Pydantic transforma tus funciones de validación en parte del propio modelo. Considera un sistema de registro de usuarios en el que los distintos niveles de suscripción tienen distintos requisitos de contraseña:El decorador
@field_validatorte da acceso a otros valores de campo a través del parámetroinfo.data, lo que permite reglas de validación que dependen de varios campos. El validador se ejecuta después de pasar la comprobación básica de tipos, por lo que puedes suponer con seguridad quesubscription_tieres uno de los valores permitidos.Para la validación que abarca varios campos, el decorador
@model_validatorse ejecuta después de validar todos los campos individuales:El parámetro
mode='after'garantiza que el validador reciba una instancia del modelo totalmente construida, lo que lo hace perfecto para la lógica empresarial que requiere acceder a todos los campos validados. El validador debe devolverselfpara indicar que la validación se ha realizado correctamente.Integración FastAPI
La integración de FastAPI con Pydantic crea la validación automática de peticiones y la documentación de la API. El patrón clave consiste en crear modelos separados para las distintas operaciones, lo que te permite controlar qué datos fluyen en cada dirección:
La separación entre modelos de entrada y de salida ofrece varias ventajas. Los modelos de entrada pueden incluir reglas de validación y campos obligatorios, mientras que los modelos de salida controlan exactamente qué datos se envían a los clientes. FastAPI genera automáticamente documentación OpenAPI a partir de tus modelos Pydantic, creando documentos de API interactivos que los programadores pueden utilizar para probar los puntos finales.
Para las operaciones de actualización, puedes crear modelos en los que todos los campos sean opcionales:
El parámetro
exclude_unset=Trueen las operaciones PATCH garantiza que sólo actualizas los campos que se proporcionaron explícitamente, evitando sobrescrituras accidentales. Este patrón funciona perfectamente para las API REST en las que los clientes envían actualizaciones parciales.Gestión de la configuración con variables de entorno
Las aplicaciones de producción necesitan una gestión de la configuración segura y fácil de implantar. La página
BaseSettingsde Pydantic, combinada con los archivos.env, proporciona una configuración segura que funciona en entornos de desarrollo, preparación y producción.Primero, crea un archivo
.enven la raíz de tu proyecto:A continuación, define tu modelo de configuración:
La clase
BaseSettingslee automáticamente las variables de entorno, los archivos.envy los argumentos de la línea de comandos. Las variables de entorno tienen prioridad sobre los valores del archivo.env, lo que facilita anular la configuración en distintos entornos de despliegue. La opcióncase_sensitive = Falsepermite nombrar las variables de entorno de forma flexible.Para aplicaciones complejas, puedes organizar los ajustes en grupos lógicos:
Este enfoque anidado mantiene juntas las configuraciones relacionadas, al tiempo que mantiene una clara separación entre los distintos componentes de tu aplicación. Cada grupo de ajustes puede tener su propio prefijo de variable de entorno y sus propias reglas de validación.
El enfoque del archivo
.envfunciona con plataformas de despliegue como Heroku, AWS y Docker, donde las variables de entorno son la forma estándar de configurar las aplicaciones. Tu aplicación obtiene seguridad y validación de tipos mientras sigue los patrones de configuración nativos de la nube que esperan los equipos de operaciones.Estos patrones constituyen la base para construir aplicaciones mantenibles que gestionen la complejidad del mundo real. El sistema de validación de Pydantic se adapta a tus requisitos específicos a la vez que proporciona mensajes de error claros y documentación automática que ayuda a todo tu equipo a comprender las estructuras de datos que espera tu aplicación.
Conclusión
Ya has visto cómo Pydantic puede ahorrarte el tedioso trabajo de escribir código de validación a mano. En lugar de saturar tus funciones con comprobaciones en
isinstance()y un tratamiento de errores personalizado, puedes definir tu estructura de datos una vez y dejar que Pydantic se encargue del resto.Asegúrate de leer la sección de Preguntas Frecuentes más abajo para preguntas más específicas sobre Pydantic. Para obtener documentación completa y patrones avanzados, aquí tienes algunos recursos que puedes utilizar: