diff --git a/src/extension.py b/src/extension.py index e69de29..a06a84e 100644 --- a/src/extension.py +++ b/src/extension.py @@ -0,0 +1,134 @@ +# extension.py + +# Este módulo permite al usuario registrar funciones personalizadas +# y luego usarlas desde otras partes del programa. +import types +import json +import inspect + +class FunctionRegistry: + """ + Clase para registrar y evaluar funciones personalizadas de forma segura. + + Asegura que las funciones registradas: + - No utilicen palabras clave peligrosas (como 'os', 'eval', etc.). + - No usen nombres reservados como 'SUM', 'IF', etc. + """ + def __init__(self): + """Inicializa el registro con funciones permitidas y palabras clave peligrosas.""" + self._functions = {} + self._reserved = {"SUM", "AVG", "IF", "MAX", "MIN"} + self._dangerous_keywords = [ + "os", "open", "eval", "exec", "__import__" + ] + + def register(self, name: str, func): + """ + Registra una función en el sistema si es segura, no usa el nombre de una función reservada y es válida. + + Parámetros: + name (str): Nombre de la función. + func (function): Objeto de función a registrar. + + Retorna: + None + """ + if name.upper() in self._reserved: + print(f"Error: El nombre '{name}' está reservado y no se puede usar.") + return + if not isinstance(func, types.FunctionType): + print(f"Error: lo que intentas registrar no es una función.") + return + + if not self.isSafe(func): + print(f"Error: lo que intentas registrar no esta permitido.") + return + + print(f"Registrando función {name}") + source = inspect.getsource(func) + self._functions[name] = {"func": func, "source": source} + print(f"Función '{name}' registrada con éxito.") + + def isSafe(self,func): + """ + Verifica si el código fuente de una función contiene palabras clave peligrosas. + + Parámetros: + func (function): Función a inspeccionar. + + Retorna: + bool: True si es segura, False si contiene código peligroso. + """ + try: + source = inspect.getsource(func) + for word in self._dangerous_keywords: + if word in source: + return False + return True + except Exception: + return False + + def evaluate(self, name, *args): + """ + Evalúa una función registrada con los argumentos dados. + + Parámetros: + name (str): Nombre de la función. + *args: Argumentos para la función. + + Retorna: + Resultado de la función, o None si hay error o no está registrada. + """ + if name not in self._functions: + print(f"Error: La función '{name}' no ha sido registrada.") + return + try: + result = self._functions[name]["func"](*args) + return result + except Exception as e: + print(f"Error: fallo al ejecutar la función '{name}':", e) + return + + def get_registered_functions(self): + """ + Retorna una lista de funciones registradas, incluyendo nombre y código fuente. + + Retorna: + list[dict]: Lista de funciones con claves 'name' y 'source'. + """ + return [ + {"name": name, "source": data["source"]} + for name, data in self._functions.items() + ] + +class FunctionSave: + """ + Clase auxiliar para guardar y cargar funciones registradas desde archivos JSON. + """ + def save_functions(self, path="functions.json"): + """ + Guarda las funciones registradas en un archivo JSON. + + Parámetros: + registry (FunctionRegistry): Registro de funciones. + path (str): Ruta del archivo JSON. + """ + with open(path, "w", encoding="utf-8") as f: + json.dump(self.get_registered_functions(), f, indent=4, ensure_ascii=False) + + def load_functions(path="functions.json"): + """ + Carga funciones desde un archivo JSON. + + Parámetros: + path (str): Ruta del archivo JSON. + + Retorna: + list[dict]: Lista de funciones con claves 'name' y 'source'. + """ + try: + with open(path, "r") as f: + data = json.load(f) + return data + except FileNotFoundError: + return [] \ No newline at end of file diff --git a/src/readme.md b/src/readme.md new file mode 100644 index 0000000..a357d32 --- /dev/null +++ b/src/readme.md @@ -0,0 +1,34 @@ +# Function Extension Module +Este módulo permite **registrar, evaluar, guardar y cargar** de forma segura funciones personalizadas provistas por el usuario. + +## Caracteristicas +- Registro de funciones Python puras. +- Validación contra palabras clave peligrosas (`os`, `eval`, `exec`, etc.). +- Prevención de nombres reservados (`SUM`, `IF`, `AVG`, etc.). +- Evaluación de funciones con argumentos dinámicos. +- Serialización y deserialización de funciones en formato JSON. + +## Estructura +- `FunctionRegistry`: Clase principal para registrar y evaluar funciones. +- `FunctionSave`: Clase auxiliar para guardar y cargar funciones desde archivos JSON. + +# Uso + +## Crear Registro +registry = FunctionRegistry() + +## Registrar una función +def suma(a, b): + return a + b + +registry.register("suma", suma) + +## Evaluar la función +resultado = registry.evaluate("suma", 2, 3) +print(resultado) # 5 + +## Guardar en JSON +FunctionSave.save_functions(registry, "mis_funciones.json") + +## Cargar del JSON +**No implementado** \ No newline at end of file diff --git a/test/test_extension.py b/test/test_extension.py new file mode 100644 index 0000000..aa525da --- /dev/null +++ b/test/test_extension.py @@ -0,0 +1,73 @@ +import sys +import os +import unittest + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'src'))) + +from extension import FunctionRegistry, FunctionSave + + +# --- Funciones de prueba --- +def suma(a, b): + return a + b + +def peligrosa(): + import os + return os.system("ls") + +def error_func(a, b): + return a / 0 # Genera error al evaluar + + +class TestFunctionRegistry(unittest.TestCase): + + def setUp(self): + self.registry = FunctionRegistry() + + def registered_names(self): + return [f["name"] for f in self.registry.get_registered_functions()] + + def test_register_success(self): + self.registry.register("mi_suma", suma) + self.assertIn("mi_suma", self.registered_names()) + + def test_register_reserved_name(self): + self.registry.register("SUM", suma) + self.assertNotIn("SUM", self.registered_names()) + + def test_register_dangerous_function(self): + self.registry.register("insegura", peligrosa) + self.assertNotIn("insegura", self.registered_names()) + + def test_register_non_function(self): + self.registry.register("no_funcion", 123) + self.assertNotIn("no_funcion", self.registered_names()) + + def test_evaluate_success(self): + self.registry.register("mi_suma", suma) + resultado = self.registry.evaluate("mi_suma", 3, 4) + self.assertEqual(resultado, 7) + + def test_evaluate_unregistered_function(self): + resultado = self.registry.evaluate("desconocida", 1, 2) + self.assertIsNone(resultado) + + def test_evaluate_function_with_error(self): + self.registry.register("error_func", error_func) + resultado = self.registry.evaluate("error_func", 1, 2) + self.assertIsNone(resultado) + + def test_save_and_load_functions(self): + self.registry.register("mi_suma", suma) + FunctionSave.save_functions(self.registry, path="test_functions.json") + loaded = FunctionSave.load_functions(path="test_functions.json") + self.assertIn("mi_suma", [f["name"] for f in loaded]) + + @classmethod + def tearDownClass(cls): + if os.path.exists("test_functions.json"): + os.remove("test_functions.json") + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file