Es común que en cualquier lenguaje de programación esperemos que los datos recibidos y enviados tengan una estructura definida. En Python existen múltiples maneras de lograr esto; como se muestra a continuación.
Viniendo de cualquier otro lenguaje de programación, algunas de las opciones que vienen a la mente son usar structs o clases que cumplan este propósito.
En el caso de Python, para facilitar esta tarea, se incluyeron las dataclasses. Estas se crean utilizando un decorador incluido en [PEP 557 - Data Classes](https://peps.python.org/pep-0557/) desde Python 3.7.
Algunas de las labores que facilitan las dataclasses son la creación de un __init__ automático que por defecto acepta los atributos de nuestra clase (con sus tipos y valores por defecto) y un __repr__ para la clase.
Ejemplo
```py
from dataclasses import dataclass
@dataclass
class Usuario:
nombre: str
edad: int
usuario = Usuario(nombre="Esteban", edad=45)
```
Hasta este punto, podemos crear estructuras con los valores que esperamos utilizar, pero hace falta algo; la validación. En el caso de dataclasses, aparte de las anotaciones de Python, en realidad no tenemos otra manera de asegurarnos de que los datos sean del tipo que esperamos, o con las restricciones que deseamos.
[Pydantic](https://docs.pydantic.dev/2.10/) soluciona esto, integrándose directamente con las anotaciones de Python. Incluyendo también mensajes de error claros en caso de que no se pueda validar correctamente, algunas clases como el PositiveInt para agregar otras restricciones a los tipos convencionales, y funcionalidades como usuario.model_dump() para mostrar campos específicos.
Ejemplo
```py
from pydantic import BaseModel, PositiveInt, ValidationError
class Usuario(BaseModel):
nombre: str
edad: PositiveInt
data = {
"nombre": "Esteban",
"edad": -45,
}
try:
usuario = Usuario(**data)
except ValidationError as e:
print(e.errors())
```
Resultado
```
[{'type': 'greater_than', 'loc': ('edad',), 'msg': 'Input should be greater than 0', 'input': -45, 'ctx': {'gt': 0}, 'url': 'https://errors.pydantic.dev/2.10/v/greater_than'}]
```
Cuando los tipos y validaciones de Pydantic no son suficientes, entra [marshmallow](https://marshmallow.readthedocs.io/en/stable/) a la escena. Marshmallow cambia las anotaciones de tipos por sus propios campos con tipos de datos con validaciones como las de URL, funciones personalizables para validación, restricciones de los valores, mensajes de error, entre otras.
Todo esto, siendo también una librería que permite serializar, de otros tipos de datos hacia nuestro schema con todas sus validaciones.
Ejemplo
```py
from marshmallow import Schema, ValidationError, fields, validate
class UsuarioSchema(Schema):
nombre = fields.Str(required=True)
edad = fields.Int(required=True, validate=validate.Range(min=0))
datos = {
"nombre": "Esteban",
"edad": -45,
}
try:
usuario = UsuarioSchema().load(datos)
except ValidationError as e:
print(e.messages)
```
Resultado
```
{'edad': ['Must be greater than or equal to 0.']}
```
Cada uno de los ejemplos cumple con funciones distintas, y cuál utilizar va a depender de la complejidad para validar los datos y la flexibilidad que se busque.