Skip to main content

Module for handler dataclass more easy

Project description

CleanDev Validator

Librería que tiene por objetivo cambiar ligeramente el comportamiento de las clases tipo @dataclass añadiendo una forma de evitar y realizar una validación previa de sus atributos según necesidad

diagrama

@dataclass

Vamos a mostrar algunos ejemplos del uso de @dataclass en python de forma nativa y posteriormente lo compararemos con los añadidos que se implementan con esta librería para que se note la intención de la misma.

Ejemplo basico

La principal causa por la que uno quisiera usar @dataclass es crear las famosas clases de estructura de datos que solo tienen la finalidad de agrupar ciertos atributos y trabajas con ellos de una forma muy concreta.

Si quisieramos una clase con atributos con el unico fin de hacer getter y setters sin usar @dataclass tendriamos algo como esto.

class Persona:
    edad: int
    altura: int
    apellidos: str
    peso: int
    nombre: str

    @property
    def edad(self):
        return self.edad

    @edad.setter
    def edad(self, edad):
        self.edad = edad

    @property
    def altura(self):
        return self.altura

    @altura.setter
    def altura(self, altura):
        self.altura = altura

    @property
    def apellidos(self):
        return self.apellidos

    @apellidos.setter
    def apellidos(self, apellidos):
        self.apellidos = apellidos

    @property
    def peso(self):
        return self.peso

    @peso.setter
    def peso(self, peso):
        self.peso = peso

    @property
    def nombre(self):
        return self.nombre

    @nombre.setter
    def nombre(self, nombre):
        self.nombre = nombre

A continuación ejecutaremos el mismo ejemplo usando @dataclass

from dataclasses import dataclass


@dataclass()
class Persona:
    edad: int
    altura: int
    apellidos: str
    peso: int
    nombre: str

Como se puede apreciar es mucho más compacto no obstante cuando queremos usar dicha clase tenemos que definir todos sus atributos si no queremos un error, o como alternativa definir un metodo __init__() que sobreescriba el comportamiento por defecto de @dataclass para permitirnos decidir que campos y cuales no queremos dar de alta.

Veamoslo en el siguiente ejemplo

from dataclasses import dataclass


@dataclass()
class Persona:
    edad: int
    altura: int
    apellidos: str
    peso: int
    nombre: str


@dataclass()
class Animal:
    peso: int
    edad: int
    nombre: str

    def __init__(self, peso: int, edad: int):
        self.peso = peso
        self.edad = edad


if __name__ == '__main__':
    # Definiendo todos sus atributos para no tener un error
    persona: Persona = Persona(edad=18, altura=176, apellidos='Hernandez', peso=80, nombre='Daniel')

    # Para el caso de animal no estamos obligados a definirle un nombre por lo que estamos obligados a definir un método
    # __init__()
    animal: Animal = Animal(peso=20, edad=10)

Existe una forma compacta de hacer el ejemplo para la clase Animal

from dataclasses import field
from dataclasses import dataclass


@dataclass()
class Animal:
    peso: int
    edad: int
    nombre: str = field(init=False)


if __name__ == '__main__':
    animal: Animal = Animal(peso=20, edad=10)

Ahora bien realmente pese a definir el tipo de dato realmente no se comprueba si el tipo de datos que se le asigna es el correcto, veamos un ejemplo

from dataclasses import field
from dataclasses import dataclass


@dataclass()
class Animal:
    peso: int
    edad: int  # Definimos como entero
    nombre: str = field(init=False)


if __name__ == '__main__':
    animal: Animal = Animal(peso=20, edad='tengo 10 años')  # ¿¿?? Le pasamos una string para edad

Ademas de esto no existe una forma sencilla de convertir la clase en dict y filtrarle algunos atributos cosa de evitarme que crear el tipico DTO (DataTransferObject) para ese fin o cosas equivalente. Buscando sobre ese caso uno de los mejores intentos de hacer esto lo encontre aquí

Ni tampoco existe una forma facil de retornar unicamente el nombre de los atributos de mi clase.

DataClass

Clase que añade dos metodos a toda clase que herede de ella que sirven para filtrar atributos de la clase y retornar los nombres de los atributos de la misa.

fields

La propiedad __fields__ de esta clase retorna los atributos de la misma.

from dataclasses import field
from dataclasses import dataclass
from cleandev_validator import DataClass


@dataclass
class Animal(DataClass):
    peso: int
    edad: int
    nombre: str = field(init=False)


if __name__ == '__main__':
    animal: Animal = Animal(peso=20, edad='tengo 10 años')
    animal.__fields__  # ['peso', 'edad', 'nombre']

filter()

Recibe como parametro un list y un bool (default=True) bool: Bandera que decide si queremos incluir o excluir los campos pasados por la lista list: Dado una lista en funcion de bool, si es True solo mostrara (k,v) de los valores pasados por la lista en caso contrario solo excluira dichos valores.

from dataclasses import field
from dataclasses import dataclass
from cleandev_validator import DataClass


@dataclass
class Animal(DataClass):
    peso: int
    edad: int
    nombre: str = field(init=False)


if __name__ == '__main__':
    animal: Animal = Animal(peso=20, edad='tengo 10 años')
    animal.__filter__(['peso'])  # {'peso': 20}
    animal.__filter__(['peso'], False)  # {'edad': 'tengo 10 años'}

validaciones de atributos

Para ejecutar la validación propuesta de atributos hay que definir un método __constrains__ de esta manera ademas de

from dataclasses import field
from dataclasses import dataclass
from cleandev_validator import DataClass, _DataClassConstrains


@dataclass()
class Animal(DataClass):
    peso: int
    edad: int
    active: bool
    nombre: str = field(init=False)

    @property
    def __constrains__(self):
        return {
            'edad': str(_DataClassConstrains.INT)
        }

Ademas de un método para que se mantegan las validaciones propias del @dataclass

def __post_init__(self):
    super(Animal, self)._validate(**self.__dict__)

El resultado final

from dataclasses import field
from dataclasses import dataclass

from cleandev_validator import DataClass, _DataClassConstrains

@dataclass()
class Animal(DataClass):
    peso: int
    edad: int
    active: bool
    nombre: str = field(init=False)

    def __post_init__(self):
        super(Animal, self)._validate(**self.__dict__)

    @property
    def __constrains__(self):
        return {
            'peso': str(_DataClassConstrains.INT),
            'edad': str(_DataClassConstrains.INT),
            'active': str(_DataClassConstrains.BOOL),
        }


if __name__ == '__main__':
    animal: Animal = Animal(peso=20, edad=20, active=True)
    animal.__filter__(['peso'])  # {'peso': 20}
    animal.__filter__(['peso'], False)  # {'edad': 'tengo 10 años'}

De esta forma estamos obligando a que realmente todos los campos son del tipo que se define y ademas de si hay que definirlo o no a la hora de crear la clase usando field(init=False)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cleandev-validator-0.3.1.tar.gz (4.3 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page