Tecnología y patitos de goma

Librería del mes - Pavlova

Con la salida de python3.7, hemos visto la inclusion de las dataclasses en la libreria standard.

¿Que es eso de las dataclasses? ¿Y que tiene que ver un postre de nueva zelanda con python? En este articulo, haremos un repaso muy rápido de las dataclasses y hablaremos de una librería de deserialización a dataclasses en python, pavlova.


META: este es el primer articulo de una serie de articulos sobre librerías que encuentro interesantes en python, no incluidas en la libreria standard


Las dataclasses son una forma ordenada, simple y pythonica de implementar clases que trabajen con datos, al estilo modelos.

Nos permiten:

  • Crear objetos con un modelo de datos especifico
  • Especificar tipos (aunque no validan)
  • Transformar como diccionario
  • Ver una representación del objeto con mucho estilo

Usando dataclasses

Como mejor vamos a ver esto, es con un ejemplo en código. para usarlas, simplemente usaremos el decorador dataclass, y type hinting.

from dataclasses import dataclass

@dataclass
class MyDataClass:
    """Simple dataclass with two elements.

    This will receive two *mandatory* arguments, elem and number.
    """
    elem: str
    number: int

Inconvenientes de las dataclasses

Las dataclasses tienen dos inconvenientes para algunos casos de uso:

  • No hace validación de tipos
  • No hay una forma comoda de cargarlas desde un diccionario

Para cargarlas desde un diccionario, podemos por ejemplo, utilizar desempaquetado para cargarlas

# Usando la dataclass definida mas arriba
mydict = dict(elem='a', number=0)
MyDataClass(**mydict)

Sin embargo, esto no soporta dataclasses anidadas.

Vamos, que si tenemos una dataclass como argumento a otra dataclass, al cargar los datos de un diccionario con otro diccionario dentro, nos vamos a quedar con... un diccionario dentro de la dataclass.

Vamos a ilustrarlo con codigo. Si tenemos un par de dataclases, una como argumento a la otra:

from dataclasses import dataclass

@dataclass
class MyDataClass:
    """Simple dataclass with two elements.

    This will receive two *mandatory* arguments, elem and number.
    """
    elem: str
    number: int

 @dataclass
 class Inheritance:
    inherit: MyDataClass

Y tenemos un diccionario tal que:

testdict = {'inherit': {'elem': 'a', 'number': 'b'}}

Cuando intentamos cargar asi el diccionario:

result = Inheritance(**testdict)

Veremos que result.inherit, contiene un diccionario. No nos ha cargado inherit como dataclass del tipo MyDataClass.

print(result.inherit)
# {'elem': 'a', 'number': 'b'}

Esto es una consecuencia de que los type hints en las dataclases son solo ilustrativos. No se utilizan por debajo. De este modo, podemos cargar tambien strings en un campo con tipo entero o vice-versa. Las dataclasses no van a hacer transformación ni validación de datos

Ahora, es cuando entra Pavlova al rescate. Que, usando el type hint con el que le especificamos los tipos hará una conversión automatica del dato para cargarlo.

Pavlova

Pavlova se encarga de deserializar a dataclasses, permitiendonos solucionar todos los problemas de los que hemos hablado (que no haya validación de tipos y no podamos cargarlas comodamente desde un diccionario).

Deserializacion de clases anidadas

Vamos a empezar probando, con Pavlova, la deserializacion de clases anidadas. Vamos a reutilizar el código que teniamos anteriormente.

from pavlova import Pavlova
with_pavlova = Pavlova().from_mapping(testdict, Inheritance)
print(with_pavlova)

Podemos ver que el resultado que nos da es:

Inheritance(inherit=MyDataClass(elem='a', number=0))

¡Pavlova soluciona nuestro primer problema!

Esto lo podemos aplicar a casting de cualquier tipo de dato, e incluso crear nuestros propios conversores.

Como hemos pavlova hace conversión de tipos con el type hinting, podemos asumir que nos va a permitir realizar ciertas validaciones de tipos.

Incluso, mediante la implementación de nuestros propios tipos de datos, podremos hacer validadores personalizados


Comprobación de tipos

Por ultimo, vamos a comprobar rapidamente si podemos hacer validación de tipos con el type hinting, en tipos mas sencillos que otra dataclass. Pavlova va a intentar convertir el tipo de dato de entrada al que le hayamos especificado en la dataclass. Podemos comprobar la validación de datos, por ejemplo, haciendo una conversion inviable.

Vamos a intentar utilizar como entero algun valor que no sea un entero valido, y veremos que Pavlova nos eleva una excepción de tipo PavlovaParsingError, y verlo en código.

Pavlova().from_mapping(dict(elem='a', number='foo'), MyDataClass)

Y el resultado

PavlovaParsingError: invalid literal for int() with base 10: 'foo'

¡Perfecto! Pavlova no solo nos ha elevado la excepción sino que eleva el mensaje de la excecpción original al aplicar el tipo especificado al dato erroneo (el equivalente al elevado por int(foo)).

¿Os parece tan sabroso como el postre?


Aquí tenemos el postre que os encontrareis si buscais "pavlova".

Pavlova es un tipo de postre elaborado de merengue denominado así en honor de Anna Pávlova. Es un pastel crujiente por fuera y muy cremoso y ligero por dentro. Los habitantes de Nueva Zelanda y los de Australia han reclamado por igual la propiedad de la receta para si, al igual que el Anzac biscuit, aunque el libro más antiguo que describe la receta fue publicado en Nueva Zelanda.

Con esto, concluimos el articulo sobre Pavlova, y la introducción a dataclasses. ¡Dejad un comentario si os ha gustado, y no olvideis subscribiros a la lista de correo!

Pavlova, un postre de nueva zelanda