feat: added new validation functions and tests
This commit is contained in:
parent
1f71d724a6
commit
8890d37a44
3 changed files with 183 additions and 8 deletions
|
|
@ -24,7 +24,9 @@ class Function(BaseModel):
|
||||||
|
|
||||||
def get_function(self):
|
def get_function(self):
|
||||||
"""Get the function"""
|
"""Get the function"""
|
||||||
return validate.eval_function(self.code)
|
function_name = validate.extract_function_name(self.code)
|
||||||
|
|
||||||
|
return validate.create_function(self.code, function_name)
|
||||||
|
|
||||||
|
|
||||||
class PythonFunction(Function):
|
class PythonFunction(Function):
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,15 @@ import types
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
|
|
||||||
|
def add_type_ignores():
|
||||||
|
if not hasattr(ast, "TypeIgnore"):
|
||||||
|
|
||||||
|
class TypeIgnore(ast.AST):
|
||||||
|
_fields = ()
|
||||||
|
|
||||||
|
ast.TypeIgnore = TypeIgnore
|
||||||
|
|
||||||
|
|
||||||
def validate_code(code):
|
def validate_code(code):
|
||||||
# Initialize the errors dictionary
|
# Initialize the errors dictionary
|
||||||
errors = {"imports": {"errors": []}, "function": {"errors": []}}
|
errors = {"imports": {"errors": []}, "function": {"errors": []}}
|
||||||
|
|
@ -16,12 +25,7 @@ def validate_code(code):
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
# Add a dummy type_ignores field to the AST
|
# Add a dummy type_ignores field to the AST
|
||||||
if not hasattr(ast, "TypeIgnore"):
|
add_type_ignores()
|
||||||
|
|
||||||
class TypeIgnore(ast.AST):
|
|
||||||
_fields = ()
|
|
||||||
|
|
||||||
ast.TypeIgnore = TypeIgnore
|
|
||||||
tree.type_ignores = []
|
tree.type_ignores = []
|
||||||
|
|
||||||
# Evaluate the import statements
|
# Evaluate the import statements
|
||||||
|
|
@ -61,3 +65,105 @@ def eval_function(function_string: str):
|
||||||
if function_object is None:
|
if function_object is None:
|
||||||
raise ValueError("Function string does not contain a function")
|
raise ValueError("Function string does not contain a function")
|
||||||
return function_object
|
return function_object
|
||||||
|
|
||||||
|
|
||||||
|
def execute_function(code, function_name, *args, **kwargs):
|
||||||
|
add_type_ignores()
|
||||||
|
|
||||||
|
module = ast.parse(code)
|
||||||
|
exec_globals = globals().copy()
|
||||||
|
|
||||||
|
for node in module.body:
|
||||||
|
if isinstance(node, ast.Import):
|
||||||
|
for alias in node.names:
|
||||||
|
try:
|
||||||
|
exec(
|
||||||
|
f"{alias.asname or alias.name} = importlib.import_module('{alias.name}')",
|
||||||
|
exec_globals,
|
||||||
|
locals(),
|
||||||
|
)
|
||||||
|
exec_globals[alias.asname or alias.name] = importlib.import_module(
|
||||||
|
alias.name
|
||||||
|
)
|
||||||
|
except ModuleNotFoundError as e:
|
||||||
|
raise ModuleNotFoundError(
|
||||||
|
f"Module {alias.name} not found. Please install it and try again."
|
||||||
|
) from e
|
||||||
|
|
||||||
|
function_code = next(
|
||||||
|
node
|
||||||
|
for node in module.body
|
||||||
|
if isinstance(node, ast.FunctionDef) and node.name == function_name
|
||||||
|
)
|
||||||
|
function_code.parent = None
|
||||||
|
code_obj = compile(
|
||||||
|
ast.Module(body=[function_code], type_ignores=[]), "<string>", "exec"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
exec(code_obj, exec_globals, locals())
|
||||||
|
except Exception as e:
|
||||||
|
# handle execution error here
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Add the function to the exec_globals dictionary
|
||||||
|
exec_globals[function_name] = locals()[function_name]
|
||||||
|
|
||||||
|
return exec_globals[function_name](*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def create_function(code, function_name):
|
||||||
|
if not hasattr(ast, "TypeIgnore"):
|
||||||
|
|
||||||
|
class TypeIgnore(ast.AST):
|
||||||
|
_fields = ()
|
||||||
|
|
||||||
|
ast.TypeIgnore = TypeIgnore
|
||||||
|
|
||||||
|
module = ast.parse(code)
|
||||||
|
exec_globals = globals().copy()
|
||||||
|
|
||||||
|
for node in module.body:
|
||||||
|
if isinstance(node, ast.Import):
|
||||||
|
for alias in node.names:
|
||||||
|
try:
|
||||||
|
exec_globals[alias.asname or alias.name] = importlib.import_module(
|
||||||
|
alias.name
|
||||||
|
)
|
||||||
|
except ModuleNotFoundError as e:
|
||||||
|
raise ModuleNotFoundError(
|
||||||
|
f"Module {alias.name} not found. Please install it and try again."
|
||||||
|
) from e
|
||||||
|
|
||||||
|
function_code = next(
|
||||||
|
node
|
||||||
|
for node in module.body
|
||||||
|
if isinstance(node, ast.FunctionDef) and node.name == function_name
|
||||||
|
)
|
||||||
|
function_code.parent = None
|
||||||
|
code_obj = compile(
|
||||||
|
ast.Module(body=[function_code], type_ignores=[]), "<string>", "exec"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
exec(code_obj, exec_globals, locals())
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
exec_globals[function_name] = locals()[function_name]
|
||||||
|
|
||||||
|
# Return a function that imports necessary modules and calls the target function
|
||||||
|
def wrapped_function(*args, **kwargs):
|
||||||
|
for module_name, module in exec_globals.items():
|
||||||
|
if isinstance(module, type(importlib)):
|
||||||
|
globals()[module_name] = module
|
||||||
|
|
||||||
|
return exec_globals[function_name](*args, **kwargs)
|
||||||
|
|
||||||
|
return wrapped_function
|
||||||
|
|
||||||
|
|
||||||
|
def extract_function_name(code):
|
||||||
|
module = ast.parse(code)
|
||||||
|
for node in module.body:
|
||||||
|
if isinstance(node, ast.FunctionDef):
|
||||||
|
return node.name
|
||||||
|
raise ValueError("No function definition found in the code string")
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,12 @@
|
||||||
from langflow.utils.validate import validate_code
|
from langflow.utils.validate import (
|
||||||
|
create_function,
|
||||||
|
extract_function_name,
|
||||||
|
validate_code,
|
||||||
|
execute_function,
|
||||||
|
)
|
||||||
|
import pytest
|
||||||
|
from requests.exceptions import MissingSchema
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
|
||||||
def test_validate_code():
|
def test_validate_code():
|
||||||
|
|
@ -37,3 +45,62 @@ def square(x)
|
||||||
"imports": {"errors": []},
|
"imports": {"errors": []},
|
||||||
"function": {"errors": ["expected ':' (<unknown>, line 4)"]},
|
"function": {"errors": ["expected ':' (<unknown>, line 4)"]},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_function_success():
|
||||||
|
code = """
|
||||||
|
import math
|
||||||
|
|
||||||
|
def my_function(x):
|
||||||
|
return math.sin(x) + 1
|
||||||
|
"""
|
||||||
|
result = execute_function(code, "my_function", 0.5)
|
||||||
|
assert result == 1.479425538604203
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_function_missing_module():
|
||||||
|
code = """
|
||||||
|
import some_missing_module
|
||||||
|
|
||||||
|
def my_function(x):
|
||||||
|
return some_missing_module.some_function(x)
|
||||||
|
"""
|
||||||
|
with pytest.raises(ModuleNotFoundError):
|
||||||
|
execute_function(code, "my_function", 0.5)
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_function_missing_function():
|
||||||
|
code = """
|
||||||
|
import math
|
||||||
|
|
||||||
|
def my_function(x):
|
||||||
|
return math.some_missing_function(x)
|
||||||
|
"""
|
||||||
|
with pytest.raises(AttributeError):
|
||||||
|
execute_function(code, "my_function", 0.5)
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_function_missing_schema():
|
||||||
|
code = """
|
||||||
|
import requests
|
||||||
|
|
||||||
|
def my_function(x):
|
||||||
|
return requests.get(x).text
|
||||||
|
"""
|
||||||
|
with mock.patch("requests.get", side_effect=MissingSchema):
|
||||||
|
with pytest.raises(MissingSchema):
|
||||||
|
execute_function(code, "my_function", "invalid_url")
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_function():
|
||||||
|
code = """
|
||||||
|
import math
|
||||||
|
|
||||||
|
def my_function(x):
|
||||||
|
return math.sin(x) + 1
|
||||||
|
"""
|
||||||
|
|
||||||
|
function_name = extract_function_name(code)
|
||||||
|
function = create_function(code, function_name)
|
||||||
|
result = function(0.5)
|
||||||
|
assert result == 1.479425538604203
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue