Skip to content

Feature/extension nueva rama con las funcionalidades del modulo extensión #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
134 changes: 134 additions & 0 deletions src/extension.py
Original file line number Diff line number Diff line change
@@ -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 []
34 changes: 34 additions & 0 deletions src/readme.md
Original file line number Diff line number Diff line change
@@ -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**
73 changes: 73 additions & 0 deletions test/test_extension.py
Original file line number Diff line number Diff line change
@@ -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()