Tengo un caso de uso en el que estoy leyendo algunos datos de una llamada a la API, pero necesito transformar los datos antes de insertarlos en una base de datos. Los datos vienen en un formato de número entero y necesito guardarlos como una cadena. La base de datos no ofrece una conversión de tipo de datos, por lo que la conversión debe realizarse en Python antes de la inserción.
Dentro de un archivo de configuración tengo como:
config = {"convert_fields": ["payment", "cash_flow"], "type": "str"} Luego, dentro de python, estoy usando la función eval() para verificar a qué tipo convertir los campos.
Entonces el código termina siendo como data['field'] = eval(config['type'])(data['field'])
¿Alguien tiene una sugerencia mejor sobre cómo puedo cambiar dinámicamente estos valores, tal vez sin almacenar el tipo de clase de Python dentro de un archivo de configuración?
Para agregar, seguro que podría hacer str() , pero puede ser necesario tener otros campos para convertir en algún momento, que no son cadenas. Así que quiero que sea dinámico, a partir de lo que esté definido en el archivo de configuración para los campos de conversión requeridos.
¿Qué tal usar getattr() y __builtins__ que creo que es un poco mejor que exec() / eval() en este caso?
def cast_by_name(type_name, value): return getattr(__builtins__, type_name)(value) print(cast_by_name("bool", 1))Debería escupir de vuelta:
TrueEs probable que desee incluir algún soporte para excepciones y quizás valores predeterminados, pero esto debería ayudarlo a comenzar.
@mistermiyagi Señala una falla crítica que, por supuesto, eval también es bulitin. Podríamos querer limitar esto a tipos seguros:
def cast_by_name(type_name, value): trusted_types = ["int", "float", "complex", "bool", "str"] ## others as needed if type_name in trusted_types: return getattr(__builtins__, type_name)(value) return value print(cast_by_name("bool", 1))Cree un diccionario de búsqueda de conversiones por adelantado.
config = {"convert_fields": {"payment" : "str", "cash_flow" : "str", "customer_id" : "int", "name" : "name_it"} } def name_it(s : str): return s.capitalize() data_in = dict( payment = 101.00, customer_id = 3, cash_flow = 1, name = "bill", city = "london" ) convert_functions = { #support builtins and custom functions fieldname : globals().get(funcname) or getattr(__builtins__, funcname) for fieldname, funcname in config["convert_fields"].items() if not funcname in {"eval"} } print(f"{convert_functions=}") data_db = { fieldname : #if no conversion is specified, use `str` convert_functions.get(fieldname, str)(value) for fieldname, value in data_in.items() } print(f"{data_db=}") convert_functions={'payment': <class 'str'>, 'cash_flow': <class 'str'>, 'customer_id': <class 'int'>, 'name': <function name_it at 0x10f0fbe20>} data_db={'payment': '101.0', 'customer_id': 3, 'cash_flow': '1', 'name': 'Bill', 'city': 'london'}si la configuración pudiera almacenarse en código, en lugar de un enfoque de tipo json, buscaría en Pydantic, aunque ese no es exactamente el espacio de su problema aquí:
from pydantic import BaseModel class Data_DB(BaseModel): payment : str customer_id : int cash_flow : str #you'd need a custom validator to handle capitalization name : str city : str pydata = Data_DB(**data_in) print(f"{pydata=}") print(pydata.dict()) pydata=Data_DB(payment='101.0', customer_id=3, cash_flow='1', name='bill', city='london') {'payment': '101.0', 'customer_id': 3, 'cash_flow': '1', 'name': 'bill', 'city': 'london'}