05Nov

Cómo validar objetos del tipo dict con la librería de validación Cerberus

Vamos a mostrar como validar objetos de tipo diccionario (dict) de Python.

Todos los ejemplos de validación se pueden descargar del repositorio Git urodoz/validacion-objetos-python-con-cerberus.

¿Qué es Cerberus?

Cerberus es una librería de validación flexible y muy ligera, que además permite extender su funcionalidad de forma sencilla. Funciona con Python 2 y 3.

Es muy útil para establecer filtros en los objetos, como por ejemplo llamadas de servicios con datos, datos de formularios, estructuras de datos, etc…

Instalando Cerberus

Podemos instalar Cerberus usando el instalador pip, debido a que está en el índice de paquetes de Python PyPI.

Ejecutamos la instalación con pip:

pip install cerberus

Validación de tipos simples: Números enteros, cadenas de caracteres y expresiones regulares

El primer ejemplo de validación que vamos a exponer es una validación simple de tipos. Supongamos que recibimos un objeto diccionario con las propiedades edad, email y nombre.

Queremos validar que los datos recibidos son correctos de tipo y contexto: una edad correcta, una dirección de correo electrónico correcta, etc…

El siguiente diccionario sería un ejemplo de objeto a validar que validará correctamente:

validation_object_001 = dict(
    age=34,
    email='urodoz@gmail.com',
    name='Albert'   
)

El siguiente diccionario sería un ejemplo de objeto que no debe pasar la validación:

validation_object_001 = dict(
    age="cincuenta",
    email='urodoz@gmail.com',
    name='Albert'   
)

En este último objeto esperamos en la propiedad age un número, y en cambio hay una cadena de caracteres.

Ahora creamos el objeto de validación para estos diccionarios cumpliendo los siguientes requisitos:

  • La edad (age) debe ser un entero mayor de 18 y menor de 120
  • El correo electrónico (email) debe parecer un correo electrónico válido
  • El nombre es una cadena de caracteres que no esté vacía.

El objeto Cerberus de validación quedaría como sigue:

cerberus_validator = Validator({
    "age": {
        "type": "integer",
        "min": 19,
        "max": 119,
        "required": True
    },
    "email": {
        "type": "string",
        "required": True,
        "regex": "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
    },
    "name": {
        "type": "string",
        "empty": False,
        "required": True
    }
})

Hemos obtenido la expresión regular de validación de correo electrónico de Emailregex.

Para una mejor validación de los correos electrónicos, recomendamos el artículo de Python Validar una dirección de correo electrónico con Python donde muestra como usar la librería syrusakbary/validate_email para este cometido.

El fichero final de prueba de los dos objetos y sus validaciones quedaría:

validacion_001.py

# -*- coding: utf-8 -*-

import unittest
from cerberus import Validator

# Objeto correcto > Validación correcta
validation_object_001 = dict(
    age=29,
    email="urodoz@gmail.com",
    name="Albert"   
)

# Objeto incorrecto > Validación incorrecta
validation_object_002 = dict(
    age="cincuenta",
    email="urodoz@gmail.com",
    name="Albert"   
)

cerberus_validator = Validator({
    "age": {
        "type": "integer",
        "min": 19,
        "max": 119,
        "required": True
    },
    "email": {
        "type": "string",
        "required": True,
        "regex": "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
    },
    "name": {
        "type": "string",
        "empty": False,
        "required": True
    }
})

class TestValidation001(unittest.TestCase):

    def test_ok_validation(self):
        self.assertTrue(cerberus_validator.validate(validation_object_001))

    def test_wrong_validation(self):
        self.assertFalse(cerberus_validator.validate(validation_object_002))


if __name__ == "__main__":
    unittest.main()

Ejecutando el archivo con Python vemos como pasa las pruebas comprobando que la validación del dato es correcto en el primer caso y no en el segundo caso:

python validacion_001.py

Validaciones de valores en listas

Para valorar una lista usamos el tipo list y la propiedad schema dentro de la descripción de la validación del elemento. En el ejemplo vamos a valorar dos listas de elementos que vienen dentro de un objeto dict con dos llaves diferentes, una con elementos de tipo número entero positivo y otra con elementos que pueden ser cadenas de caracteres o cualquier número entero.

Ejemplos de objeto válido e inválidos serían los siguientes:

validacion_002.py

# Objeto correcto
list_validation_object_ok = {
    "list_a": [1, 5, 10, 99, 400],
    "list_b": ["foo", "bar", 1]
}

# Objeto inválido 1
list_validation_object_wrong = {
    "list_a": [3, 6, -9, 100],
    "list_b": ["simple", "validation", -9]
}

# Objeto inválido 2
list_validation_object_wrong_2 = {
    "list_a": [1, 5, 10, 99, 400],
    "list_b": ["simple", "validation", None]
}

Para el tipo de lista list_b creamos un método validador personalizado. La clase de ejemplo quedaría como sigue:

# -*- coding: utf-8 -*-

import unittest
from cerberus import Validator

# Objeto correcto
list_validation_object_ok = {
    "list_a": [1, 5, 10, 99, 400],
    "list_b": ["foo", "bar", 1]
}

# Objeto inválido 1
list_validation_object_wrong = {
    "list_a": [3, 6, -9, 100],
    "list_b": ["simple", "validation", -9]
}

# Objeto inválido 2
list_validation_object_wrong_2 = {
    "list_a": [1, 5, 10, 99, 400],
    "list_b": ["simple", "validation", None]
}

def string_or_integer_list(field, value, error):
    if not isinstance(value, list):
        error(field, u'No es una lista')
    else:
        for item in value:
            if isinstance(item, int) or isinstance(item, str):
                # Test ok: es un valor válido
                pass
            else:
                error(field, u'No es válido')

class TestValidation002(unittest.TestCase):
    
    def factory_validator(self):
        return Validator({
            "list_a": {
                "type": "list",
                "required": True,
                "schema": {
                    "type": "integer",
                    "min": 1
                }
            },
            "list_b": {
                "type": "list",
                "validator": string_or_integer_list
            },
        })

    def test_ok_validation(self):
        self.assertTrue(self.factory_validator().validate(list_validation_object_ok))

    def test_wrong_validation(self):
        self.assertFalse(self.factory_validator().validate(list_validation_object_wrong))
    
    def test_wrong_validation_2(self):
        self.assertFalse(self.factory_validator().validate(list_validation_object_wrong_2))


if __name__ == "__main__":
    unittest.main()

Más información

Recomendamos leer las diferentes normas de validación en su página Validation Rules para aprender más sobre la colección de Normas de validación que hay disponible en esta librería.

También recomendamos la visualización de este vídeo de presentación de Pycon UK 2016 donde el creador presenta la funcionalidad de su librería.

Repositorio Github de Cerberus: https://github.com/nicolaiarocci/cerberus/

Página Oficial de la librería Cerberus: http://docs.python-cerberus.org/en/stable/

Leave a comment