FastAPI

FastAPI

Tutorial FastAPI: Validación de datos con Pydantic 2

Aprende a validar y gestionar datos con Pydantic 2 en Python, integrando modelos robustos y validaciones avanzadas para APIs con FastAPI.

Aprende FastAPI y certifícate

Introducción a Pydantic y modelos de datos

Pydantic es una biblioteca de Python que facilita la validación de datos y la gestión de configuraciones mediante la definición de modelos basados en tipos. En el contexto de FastAPI, Pydantic se convierte en una herramienta fundamental para validar los datos que reciben nuestros endpoints y para serializar las respuestas que enviamos a los clientes.

¿Qué es Pydantic?

Pydantic es una biblioteca de validación que aprovecha las anotaciones de tipo de Python para proporcionar validación de datos en tiempo de ejecución. A diferencia de otras soluciones, Pydantic valida los datos durante la creación de instancias de modelos, lo que garantiza que los datos sean consistentes y cumplan con las restricciones definidas.

La versión 2 de Pydantic, que utilizaremos en este curso, introduce mejoras significativas en rendimiento y nuevas funcionalidades que hacen que la validación de datos sea aún más robusta y flexible.

Instalación de Pydantic

Para comenzar a trabajar con Pydantic, necesitamos instalarlo en nuestro entorno:

pip install pydantic

FastAPI ya incluye Pydantic como dependencia, por lo que si has instalado FastAPI, probablemente ya tengas Pydantic disponible. Sin embargo, para asegurarte de tener la versión 2, puedes actualizarla:

pip install -U pydantic

Modelos básicos en Pydantic

Un modelo en Pydantic es simplemente una clase que hereda de BaseModel. Esta clase define la estructura de los datos que queremos validar, utilizando anotaciones de tipo de Python.

Veamos un ejemplo básico:

from pydantic import BaseModel
from typing import Optional

class User(BaseModel):
    id: int
    name: str
    email: str
    active: bool = True
    bio: Optional[str] = None

En este ejemplo:

  • Creamos un modelo User que hereda de BaseModel
  • Definimos campos con sus tipos esperados (int, str, bool)
  • Establecemos valores predeterminados para algunos campos (active = True)
  • Utilizamos Optional para indicar que un campo puede ser None

Creación y validación de instancias

Una vez definido el modelo, podemos crear instancias y Pydantic se encargará de validar automáticamente los datos:

# Datos válidos
user1 = User(id=1, name="Ana García", email="ana@ejemplo.com")
print(user1)
# User(id=1, name='Ana García', email='ana@ejemplo.com', active=True, bio=None)

# Datos inválidos - intentando asignar un string a un campo int
try:
    user2 = User(id="no-es-un-numero", name="Carlos López", email="carlos@ejemplo.com")
except Exception as e:
    print(f"Error: {e}")
    # Error: 1 validation error for User
    # id
    #   Input should be a valid integer [type=int_type, input_value='no-es-un-numero', input_type=str]

Pydantic no solo valida los tipos, sino que también intenta convertir los datos al tipo esperado cuando es posible:

# Conversión automática de tipos
user3 = User(id="42", name="Elena Martínez", email="elena@ejemplo.com")
print(user3)
# User(id=42, name='Elena Martínez', email='elena@ejemplo.com', active=True, bio=None)
print(type(user3.id))  # <class 'int'> - Pydantic ha convertido el string "42" a int

Modelos anidados

Pydantic permite definir modelos anidados, lo que es muy útil para representar estructuras de datos complejas:

from pydantic import BaseModel
from typing import List, Optional

class Address(BaseModel):
    street: str
    city: str
    country: str
    postal_code: str

class User(BaseModel):
    id: int
    name: str
    email: str
    addresses: List[Address] = []
    primary_address: Optional[Address] = None

Podemos crear instancias con datos anidados:

# Creando un usuario con direcciones
user = User(
    id=1,
    name="Miguel Sánchez",
    email="miguel@ejemplo.com",
    addresses=[
        {"street": "Calle Mayor 10", "city": "Madrid", "country": "España", "postal_code": "28001"},
        {"street": "Avenida Libertad 25", "city": "Barcelona", "country": "España", "postal_code": "08001"}
    ],
    primary_address={"street": "Calle Mayor 10", "city": "Madrid", "country": "España", "postal_code": "28001"}
)

print(user.primary_address.city)  # Madrid
print(len(user.addresses))  # 2

Serialización y deserialización

Pydantic facilita la conversión entre objetos Python y formatos como JSON, lo que es esencial en el desarrollo de APIs:

# Serialización a diccionario
user_dict = user.model_dump()
print(type(user_dict))  # <class 'dict'>

# Serialización a JSON
user_json = user.model_dump_json()
print(type(user_json))  # <class 'str'>
print(user_json[:50])  # {"id":1,"name":"Miguel Sánchez","email":"miguel@eje...

# Deserialización desde diccionario
new_user = User.model_validate(user_dict)
print(type(new_user))  # <class 'User'>

Integración con FastAPI

En FastAPI, los modelos de Pydantic se utilizan para:

  1. Validar datos de entrada: Definiendo los parámetros de las rutas
  2. Documentar la API: Generando automáticamente la documentación OpenAPI
  3. Serializar respuestas: Convirtiendo objetos Python a JSON

Veamos un ejemplo básico de cómo se integra Pydantic con FastAPI:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = 0.0

@app.post("/items/")
async def create_item(item: Item):
    # FastAPI valida automáticamente que los datos recibidos
    # cumplan con el modelo Item
    
    # Podemos acceder a los campos validados
    price_with_tax = item.price + item.tax
    
    # Y devolver un diccionario o un modelo Pydantic
    return {
        "item_name": item.name,
        "price_with_tax": price_with_tax
    }

En este ejemplo:

  • Definimos un modelo Item con Pydantic
  • FastAPI usa este modelo para validar automáticamente el cuerpo de la solicitud POST
  • Si los datos no son válidos, FastAPI devuelve un error 422 con detalles sobre la validación fallida
  • Si los datos son válidos, podemos acceder a los campos ya validados y convertidos

Ventajas de usar Pydantic en FastAPI

El uso de Pydantic en FastAPI proporciona varias ventajas clave:

  • Validación automática: No necesitas escribir código para validar manualmente los datos
  • Conversión de tipos: Los datos se convierten automáticamente a los tipos Python adecuados
  • Documentación automática: FastAPI genera documentación OpenAPI basada en tus modelos
  • Serialización/deserialización: Facilita la conversión entre JSON y objetos Python
  • Seguridad: Ayuda a prevenir errores y vulnerabilidades relacionadas con datos incorrectos

Modelos para respuestas

También podemos usar modelos Pydantic para definir la estructura de las respuestas:

from fastapi import FastAPI
from pydantic import BaseModel
from typing import List

app = FastAPI()

class ItemBase(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = 0.0

class ItemCreate(ItemBase):
    # Modelo para la creación - hereda de ItemBase
    pass

class Item(ItemBase):
    # Modelo para la respuesta - añade un id
    id: int

@app.post("/items/", response_model=Item)
async def create_item(item: ItemCreate):
    # Simulamos la creación de un item en la base de datos
    # y le asignamos un id
    new_item = Item(
        id=1,
        name=item.name,
        description=item.description,
        price=item.price,
        tax=item.tax
    )
    return new_item

El parámetro response_model le indica a FastAPI que debe validar y filtrar la respuesta según el modelo especificado, lo que garantiza que la respuesta tenga la estructura esperada.

Configuración de modelos

Pydantic permite configurar el comportamiento de los modelos mediante la clase Config:

from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str
    email: str
    password: str

    class Config:
        # Excluir campos sensibles al serializar a JSON
        json_schema_extra = {
            "example": {
                "id": 1,
                "name": "Usuario Ejemplo",
                "email": "usuario@ejemplo.com",
                "password": "contraseña123"
            }
        }
        # No incluir valores None en la serialización
        exclude_none = True
        # Permitir nombres de campo con guiones bajos en JSON
        populate_by_name = True

Esta configuración afecta a cómo se comporta el modelo al validar datos y al serializarlos.

Campos computados

Pydantic 2 introduce el concepto de campos computados que permiten definir propiedades que se calculan a partir de otros campos:

from pydantic import BaseModel, computed_field

class Product(BaseModel):
    name: str
    price: float
    tax_rate: float = 0.21

    @computed_field
    def price_with_tax(self) -> float:
        return self.price * (1 + self.tax_rate)

product = Product(name="Laptop", price=1000)
print(product.price_with_tax)  # 1210.0

Los campos computados se incluyen en la serialización del modelo, lo que los hace útiles para enviar datos derivados en las respuestas de la API.

Tipos de datos y validaciones

Pydantic ofrece un sistema robusto de validación que va mucho más allá de la simple comprobación de tipos. En esta sección exploraremos los diferentes tipos de datos que podemos utilizar en nuestros modelos y las validaciones específicas que podemos aplicar para garantizar la integridad de los datos.

Tipos básicos de Python

Pydantic soporta todos los tipos nativos de Python, permitiéndonos definir modelos con una estructura clara y tipada:

from pydantic import BaseModel
from datetime import datetime, date
from typing import Optional

class Product(BaseModel):
    id: int
    name: str
    price: float
    is_available: bool
    created_at: datetime
    release_date: date
    tags: list[str]
    metadata: dict[str, str] = {}
    discount: Optional[float] = None

Cuando creamos una instancia de este modelo, Pydantic validará automáticamente que cada campo reciba un valor del tipo correcto:

# Ejemplo de uso con datos válidos
product = Product(
    id=1,
    name="Teclado ergonómico",
    price=89.99,
    is_available=True,
    created_at="2023-10-15T14:30:00",  # Se convertirá automáticamente a datetime
    release_date="2023-10-20",         # Se convertirá automáticamente a date
    tags=["ergonómico", "mecánico", "inalámbrico"]
)

print(product.created_at)  # datetime.datetime(2023, 10, 15, 14, 30)
print(type(product.created_at))  # <class 'datetime.datetime'>

Validaciones con Field

Para aplicar restricciones adicionales a los campos, Pydantic proporciona la función Field, que permite definir validaciones específicas:

from pydantic import BaseModel, Field

class Product(BaseModel):
    id: int = Field(gt=0, description="ID único del producto")
    name: str = Field(min_length=3, max_length=50)
    price: float = Field(gt=0, lt=10000)
    stock: int = Field(ge=0, le=1000)
    description: str = Field(default="", max_length=1000)
    sku: str = Field(pattern=r"^[A-Z]{2}-\d{4}$")

En este ejemplo:

  • gt y lt: Mayor que (greater than) y menor que (less than)
  • ge y le: Mayor o igual que (greater or equal) y menor o igual que (less or equal)
  • min_length y max_length: Longitud mínima y máxima para strings
  • pattern: Expresión regular que debe cumplir el string
  • description: Documentación del campo (útil para la generación de OpenAPI)

Tipos complejos y genéricos

Pydantic soporta tipos genéricos de Python, lo que nos permite definir estructuras de datos más complejas:

from pydantic import BaseModel
from typing import List, Dict, Set, Tuple, Union

class ComplexModel(BaseModel):
    tags: List[str]  # Lista de strings
    counts: Dict[str, int]  # Diccionario con claves string y valores enteros
    unique_ids: Set[int]  # Conjunto de enteros
    coordinates: Tuple[float, float]  # Tupla de dos flotantes
    value: Union[int, str]  # Puede ser entero o string

En Python 3.9+, podemos usar la sintaxis simplificada:

class ComplexModel(BaseModel):
    tags: list[str]
    counts: dict[str, int]
    unique_ids: set[int]
    coordinates: tuple[float, float]
    value: int | str  # Union simplificado en Python 3.10+

Validaciones personalizadas con validators

Para casos donde necesitamos lógica de validación personalizada, podemos usar decoradores @field_validator (para un campo específico) o @model_validator (para validar el modelo completo):

from pydantic import BaseModel, field_validator, model_validator

class User(BaseModel):
    username: str
    email: str
    password: str
    password_confirm: str

    @field_validator('username')
    @classmethod
    def username_alphanumeric(cls, v: str) -> str:
        if not v.isalnum():
            raise ValueError('El nombre de usuario debe ser alfanumérico')
        return v
    
    @field_validator('email')
    @classmethod
    def email_domain(cls, v: str) -> str:
        if not v.endswith(('.com', '.es', '.org')):
            raise ValueError('Dominio de correo no válido')
        return v
    
    @model_validator(mode='after')
    def check_passwords_match(self) -> 'User':
        if self.password != self.password_confirm:
            raise ValueError('Las contraseñas no coinciden')
        return self

Observa que:

  • Los validadores de campo reciben el valor a validar y deben devolver el valor (posiblemente modificado)
  • El validador de modelo recibe la instancia completa y puede acceder a múltiples campos
  • Usamos @classmethod para los validadores de campo, ya que se ejecutan antes de crear la instancia
  • El parámetro mode='after' indica que el validador se ejecuta después de que todos los campos han sido validados

Tipos especiales de Pydantic

Pydantic proporciona tipos especializados para validaciones comunes:

from pydantic import (
    BaseModel, EmailStr, HttpUrl, 
    SecretStr, PositiveInt, NegativeFloat,
    constr, confloat, conint
)

class SpecialTypes(BaseModel):
    email: EmailStr  # Valida formato de email
    website: HttpUrl  # Valida URL HTTP/HTTPS
    password: SecretStr  # Oculta el valor en la representación string
    count: PositiveInt  # Entero positivo
    temperature: NegativeFloat  # Flotante negativo
    
    # Tipos con restricciones
    username: constr(min_length=3, max_length=20, pattern=r'^[a-zA-Z0-9_]+$')
    score: confloat(ge=0, le=100)
    age: conint(ge=18, lt=120)

Para usar EmailStr y HttpUrl, necesitamos instalar dependencias adicionales:

pip install pydantic[email]

Enumeraciones

Las enumeraciones son útiles para restringir un campo a un conjunto específico de valores:

from enum import Enum
from pydantic import BaseModel

class UserRole(str, Enum):
    ADMIN = "admin"
    EDITOR = "editor"
    VIEWER = "viewer"

class PaymentMethod(str, Enum):
    CREDIT_CARD = "credit_card"
    PAYPAL = "paypal"
    BANK_TRANSFER = "bank_transfer"

class User(BaseModel):
    id: int
    name: str
    role: UserRole = UserRole.VIEWER

class Payment(BaseModel):
    amount: float
    method: PaymentMethod

Al usar enumeraciones:

user = User(id=1, name="Ana", role=UserRole.ADMIN)
# También funciona con strings que coincidan con los valores de la enumeración
user2 = User(id=2, name="Carlos", role="editor")

# Esto lanzaría un error de validación
try:
    user3 = User(id=3, name="Elena", role="superuser")
except Exception as e:
    print(f"Error: {e}")

Tipos de fecha y hora

Pydantic maneja automáticamente la conversión y validación de fechas y horas:

from datetime import datetime, date, time, timedelta
from pydantic import BaseModel

class EventSchedule(BaseModel):
    name: str
    start_date: date
    end_date: date
    start_time: time
    duration: timedelta
    created_at: datetime

# Podemos proporcionar strings en formatos estándar
event = EventSchedule(
    name="Conferencia Python",
    start_date="2023-11-15",  # ISO format
    end_date="2023-11-17",
    start_time="09:00:00",
    duration="2:30:00",  # 2 horas y 30 minutos
    created_at="2023-10-01T14:30:00+02:00"  # ISO format con zona horaria
)

print(event.start_date)  # 2023-11-15
print(type(event.start_date))  # <class 'datetime.date'>
print(event.duration.total_seconds() / 3600)  # 2.5 (horas)

Validación de listas y diccionarios

Podemos aplicar restricciones específicas a colecciones:

from pydantic import BaseModel, Field
from typing import List, Dict

class Inventory(BaseModel):
    # Lista con restricción de longitud
    categories: List[str] = Field(min_length=1, max_length=10)
    
    # Diccionario con valores restringidos
    stock: Dict[str, int] = Field(default_factory=dict)
    
    @field_validator('stock')
    @classmethod
    def check_positive_stock(cls, v: Dict[str, int]) -> Dict[str, int]:
        for item, quantity in v.items():
            if quantity < 0:
                raise ValueError(f"El stock de {item} no puede ser negativo")
        return v

Modelos con campos discriminadores

Para casos donde necesitamos polimorfismo, Pydantic ofrece discriminadores:

from pydantic import BaseModel, Field
from typing import Literal, Union, Annotated

class BasePet(BaseModel):
    pet_type: str
    name: str

class Dog(BasePet):
    pet_type: Literal["dog"]
    breed: str
    bark_loudness: int = 5

class Cat(BasePet):
    pet_type: Literal["cat"]
    breed: str
    meow_frequency: float

# Usando discriminador
Pet = Annotated[Union[Dog, Cat], Field(discriminator='pet_type')]

def process_pet(pet: Pet):
    if isinstance(pet, Dog):
        print(f"{pet.name} es un perro que ladra con intensidad {pet.bark_loudness}")
    elif isinstance(pet, Cat):
        print(f"{pet.name} es un gato que maúlla {pet.meow_frequency} veces por hora")

# Uso
dog_data = {
    "pet_type": "dog",
    "name": "Rex",
    "breed": "Labrador",
    "bark_loudness": 8
}

cat_data = {
    "pet_type": "cat",
    "name": "Whiskers",
    "breed": "Siamese",
    "meow_frequency": 12.5
}

process_pet(Pet.model_validate(dog_data))
process_pet(Pet.model_validate(cat_data))

Validación condicional

Para implementar validaciones que dependen de otros campos, podemos combinar validadores de modelo con lógica condicional:

from pydantic import BaseModel, field_validator, model_validator
from typing import Optional

class Discount(BaseModel):
    type: str  # "percentage" o "fixed"
    value: float
    max_amount: Optional[float] = None

    @model_validator(mode='after')
    def check_discount_constraints(self) -> 'Discount':
        if self.type == "percentage":
            if not 0 <= self.value <= 100:
                raise ValueError("El porcentaje debe estar entre 0 y 100")
        elif self.type == "fixed":
            if self.value <= 0:
                raise ValueError("El descuento fijo debe ser positivo")
        else:
            raise ValueError("Tipo de descuento no válido")
        
        # Validación condicional
        if self.type == "percentage" and self.max_amount is not None:
            if self.max_amount <= 0:
                raise ValueError("El monto máximo debe ser positivo")
        
        return self

Integración con FastAPI

En FastAPI, estos modelos y validaciones se utilizan directamente en las definiciones de rutas:

from fastapi import FastAPI, HTTPException, Path, Query
from pydantic import BaseModel, EmailStr, Field, field_validator
from enum import Enum
from typing import List, Optional

app = FastAPI()

class UserRole(str, Enum):
    ADMIN = "admin"
    USER = "user"

class UserCreate(BaseModel):
    username: str = Field(min_length=3, max_length=50)
    email: EmailStr
    password: str = Field(min_length=8)
    role: UserRole = UserRole.USER
    
    @field_validator('username')
    @classmethod
    def username_alphanumeric(cls, v: str) -> str:
        if not v.isalnum():
            raise ValueError('El nombre de usuario debe ser alfanumérico')
        return v

class UserResponse(BaseModel):
    id: int
    username: str
    email: EmailStr
    role: UserRole

@app.post("/users/", response_model=UserResponse)
async def create_user(user: UserCreate):
    # Aquí iría la lógica para crear el usuario en la base de datos
    # Simulamos la creación asignando un ID
    return UserResponse(
        id=1,
        username=user.username,
        email=user.email,
        role=user.role
    )

@app.get("/users/{user_id}")
async def get_user(
    user_id: int = Path(..., gt=0, description="ID del usuario"),
    include_inactive: bool = Query(False, description="Incluir usuarios inactivos")
):
    # Aquí iría la lógica para obtener el usuario de la base de datos
    if user_id == 999 and not include_inactive:
        raise HTTPException(status_code=404, detail="Usuario no encontrado")
    
    return {"id": user_id, "username": "usuario_ejemplo", "active": not include_inactive}

En este ejemplo:

  • Usamos Field para validar los campos del modelo UserCreate
  • Aplicamos un validador personalizado para el nombre de usuario
  • Definimos un modelo separado UserResponse para la respuesta
  • Utilizamos Path y Query para validar parámetros de ruta y consulta

Validación de archivos y contenido binario

Pydantic también puede validar contenido binario y archivos:

from pydantic import BaseModel, field_validator
from fastapi import FastAPI, File, UploadFile
from typing import List

app = FastAPI()

class ImageMetadata(BaseModel):
    filename: str
    content_type: str
    size: int
    
    @field_validator('content_type')
    @classmethod
    def validate_image_type(cls, v: str) -> str:
        allowed_types = ["image/jpeg", "image/png", "image/gif"]
        if v not in allowed_types:
            raise ValueError(f"Tipo de imagen no permitido. Debe ser uno de: {allowed_types}")
        return v
    
    @field_validator('size')
    @classmethod
    def validate_size(cls, v: int) -> int:
        max_size = 5 * 1024 * 1024  # 5MB
        if v > max_size:
            raise ValueError(f"El archivo es demasiado grande. Máximo: 5MB")
        return v

@app.post("/upload/")
async def upload_image(image: UploadFile = File(...)):
    # Validamos los metadatos del archivo
    metadata = ImageMetadata(
        filename=image.filename,
        content_type=image.content_type,
        size=len(await image.read())
    )
    
    # Importante: resetear la posición del archivo después de leerlo
    await image.seek(0)
    
    # Procesar el archivo...
    
    return {"filename": metadata.filename, "size": metadata.size}

Este enfoque nos permite validar aspectos como el tipo MIME y el tamaño del archivo antes de procesarlo.

Validación de JSON anidado

Para validar estructuras JSON complejas y anidadas, podemos combinar modelos:

from pydantic import BaseModel, Field
from typing import List, Dict, Any, Optional

class Address(BaseModel):
    street: str
    city: str
    postal_code: str
    country: str

class OrderItem(BaseModel):
    product_id: int
    quantity: int = Field(gt=0)
    unit_price: float = Field(gt=0)
    
    @property
    def total_price(self) -> float:
        return self.quantity * self.unit_price

class Order(BaseModel):
    id: int
    customer_id: int
    items: List[OrderItem] = Field(min_length=1)
    shipping_address: Address
    billing_address: Optional[Address] = None
    metadata: Dict[str, Any] = {}
    
    @model_validator(mode='after')
    def set_billing_address(self) -> 'Order':
        # Si no se proporciona dirección de facturación, usar la de envío
        if self.billing_address is None:
            self.billing_address = self.shipping_address
        return self
    
    @property
    def total_amount(self) -> float:
        return sum(item.total_price for item in self.items)

Este modelo puede validar estructuras JSON complejas como:

order_data = {
    "id": 12345,
    "customer_id": 42,
    "items": [
        {"product_id": 101, "quantity": 2, "unit_price": 29.99},
        {"product_id": 205, "quantity": 1, "unit_price": 59.50}
    ],
    "shipping_address": {
        "street": "Calle Gran Vía 123",
        "city": "Madrid",
        "postal_code": "28013",
        "country": "España"
    },
    "metadata": {
        "source": "web",
        "promotion_code": "SUMMER2023"
    }
}

order = Order.model_validate(order_data)
print(f"Total del pedido: {order.total_amount}€")
print(f"Dirección de facturación: {order.billing_address.city}")  # Madrid (copiada de shipping_address)

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

Plan mensual

19.00 € /mes

Precio normal mensual: 19 €
47 % DE DESCUENTO

Plan anual

10.00 € /mes

Ahorras 108 € al año
Precio normal anual: 120 €
Aprende FastAPI online

Ejercicios de esta lección Validación de datos con Pydantic 2

Evalúa tus conocimientos de esta lección Validación de datos con Pydantic 2 con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Todas las lecciones de FastAPI

Accede a todas las lecciones de FastAPI y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a FastAPI y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender qué es Pydantic y su importancia en la validación de datos.
  • Aprender a definir modelos de datos con Pydantic y validar instancias.
  • Utilizar tipos básicos, complejos y validaciones personalizadas en modelos.
  • Integrar modelos Pydantic con FastAPI para validar entradas y respuestas.
  • Aplicar configuraciones avanzadas, campos computados y validaciones condicionales.