Package: cleandev-validator
CleanDev ValidatorLibrería que tiene por objetivo cambiar ligeramente el comportamiento de las clases tipo@dataclassañadiendo
una forma de evitar y realizar una validación previa de sus atributos según necesidad@dataclassVamos a mostrar algunos ejemplos del uso de@dataclassen 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 basicoLa principal causa por la que uno quisiera usar@dataclasses 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@dataclasstendriamos algo
como esto.classPersona:edad:intaltura:intapellidos:strpeso:intnombre:str@propertydefedad(self):returnself.edad@edad.setterdefedad(self,edad):self.edad=edad@propertydefaltura(self):returnself.altura@altura.setterdefaltura(self,altura):self.altura=altura@propertydefapellidos(self):returnself.apellidos@apellidos.setterdefapellidos(self,apellidos):self.apellidos=apellidos@propertydefpeso(self):returnself.peso@peso.setterdefpeso(self,peso):self.peso=peso@propertydefnombre(self):returnself.nombre@nombre.setterdefnombre(self,nombre):self.nombre=nombreA continuación ejecutaremos el mismo ejemplo usando@dataclassfromdataclassesimportdataclass@dataclass()classPersona:edad:intaltura:intapellidos:strpeso:intnombre:strComo 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@dataclasspara permitirnos decidir que campos y cuales no queremos dar de alta.Veamoslo en el siguiente ejemplofromdataclassesimportdataclass@dataclass()classPersona:edad:intaltura:intapellidos:strpeso:intnombre:str@dataclass()classAnimal:peso:intedad:intnombre:strdef__init__(self,peso:int,edad:int):self.peso=pesoself.edad=edadif__name__=='__main__':# Definiendo todos sus atributos para no tener un errorpersona: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 claseAnimalfromdataclassesimportfieldfromdataclassesimportdataclass@dataclass()classAnimal:peso:intedad:intnombre: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 ejemplofromdataclassesimportfieldfromdataclassesimportdataclass@dataclass()classAnimal:peso:intedad:int# Definimos como enteronombre:str=field(init=False)if__name__=='__main__':animal:Animal=Animal(peso=20,edad='tengo 10 años')# ¿¿?? Le pasamos una string para edadAdemas de esto no existe una forma sencilla de convertir la clase endicty 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
encontreaquíNi tampoco existe una forma facil de retornar unicamente el nombre de los atributos de mi clase.DataClassClase 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.fieldsLa propiedad__fields__de esta clase retorna los atributos de la misma.fromdataclassesimportfieldfromdataclassesimportdataclassfromcleandev_validatorimportDataClass@dataclassclassAnimal(DataClass):peso:intedad:intnombre: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 unlisty unbool(default=True)bool: Bandera que decide si queremos incluir o excluir los campos pasados por la listalist: Dado una lista en funcion debool, si esTruesolo mostrara (k,v) de los valores pasados por la lista
en caso contrario solo excluira dichos valores.fromdataclassesimportfieldfromdataclassesimportdataclassfromcleandev_validatorimportDataClass@dataclassclassAnimal(DataClass):peso:intedad:intnombre: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 atributosPara ejecutar la validación propuesta de atributos hay que definir un método__constrains__de esta manera ademas defromdataclassesimportfieldfromdataclassesimportdataclassfromcleandev_validatorimportDataClass,_DataClassConstrains@dataclass()classAnimal(DataClass):peso:intedad:intactive:boolnombre:str=field(init=False)@propertydef__constrains__(self):return{'edad':str(_DataClassConstrains.INT)}Ademas de un método para que se mantegan las validaciones propias del@dataclassdef__post_init__(self):super(Animal,self)._validate(**self.__dict__)El resultado finalfromdataclassesimportfieldfromdataclassesimportdataclassfromcleandev_validatorimportDataClass,_DataClassConstrains@dataclass()classAnimal(DataClass):peso:intedad:intactive:boolnombre:str=field(init=False)def__post_init__(self):super(Animal,self)._validate(**self.__dict__)@propertydef__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 usandofield(init=False)