feat: Allow variables to be defined outside a Component (#4316)
* feat: Allow variables to be defined outside a Component Fixes #4315 Modify `prepare_global_scope` function in `src/backend/base/langflow/utils/validate.py` to evaluate classes, functions, and variables defined outside the Component's class. * **Function Changes:** - Add evaluation of external classes using `ast.ClassDef`. - Add evaluation of external functions using `ast.FunctionDef`. - Add evaluation of external variables using `ast.Assign`. * **Unit Tests:** - Add tests in `src/backend/tests/unit/test_validate_code.py` to verify the evaluation of external classes, functions, and variables. - Include tests for multiple external classes and external variables and functions. --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/langflow-ai/langflow/issues/4315?shareId=XXXX-XXXX-XXXX-XXXX). * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
fb0084d478
commit
ccba14bec4
4 changed files with 84 additions and 1 deletions
|
|
@ -34,6 +34,9 @@
|
||||||
"GitHub.vscode-pull-request-github"
|
"GitHub.vscode-pull-request-github"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"tasks": {
|
||||||
|
"test": "make unit_tests"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||||
|
|
|
||||||
BIN
localhost-19-2.db
Normal file
BIN
localhost-19-2.db
Normal file
Binary file not shown.
|
|
@ -227,6 +227,16 @@ def prepare_global_scope(code, module):
|
||||||
except ModuleNotFoundError as e:
|
except ModuleNotFoundError as e:
|
||||||
msg = f"Module {node.module} not found. Please install it and try again"
|
msg = f"Module {node.module} not found. Please install it and try again"
|
||||||
raise ModuleNotFoundError(msg) from e
|
raise ModuleNotFoundError(msg) from e
|
||||||
|
elif isinstance(node, ast.ClassDef):
|
||||||
|
# Compile and execute the class definition to properly create the class
|
||||||
|
class_code = compile(ast.Module(body=[node], type_ignores=[]), "<string>", "exec")
|
||||||
|
exec(class_code, exec_globals)
|
||||||
|
elif isinstance(node, ast.FunctionDef):
|
||||||
|
function_code = compile(ast.Module(body=[node], type_ignores=[]), "<string>", "exec")
|
||||||
|
exec(function_code, exec_globals)
|
||||||
|
elif isinstance(node, ast.Assign):
|
||||||
|
assign_code = compile(ast.Module(body=[node], type_ignores=[]), "<string>", "exec")
|
||||||
|
exec(assign_code, exec_globals)
|
||||||
return exec_globals
|
return exec_globals
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,13 @@ from pathlib import Path
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from langflow.utils.validate import create_function, execute_function, extract_function_name, validate_code
|
from langflow.utils.validate import (
|
||||||
|
create_class,
|
||||||
|
create_function,
|
||||||
|
execute_function,
|
||||||
|
extract_function_name,
|
||||||
|
validate_code,
|
||||||
|
)
|
||||||
from requests.exceptions import MissingSchema
|
from requests.exceptions import MissingSchema
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -100,3 +106,67 @@ def my_function(x):
|
||||||
"""
|
"""
|
||||||
with mock.patch("requests.get", side_effect=MissingSchema), pytest.raises(MissingSchema):
|
with mock.patch("requests.get", side_effect=MissingSchema), pytest.raises(MissingSchema):
|
||||||
execute_function(code, "my_function", "invalid_url")
|
execute_function(code, "my_function", "invalid_url")
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_class():
|
||||||
|
code = """
|
||||||
|
from langflow.custom import CustomComponent
|
||||||
|
|
||||||
|
class ExternalClass:
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
class MyComponent(CustomComponent):
|
||||||
|
def build(self):
|
||||||
|
return ExternalClass("test")
|
||||||
|
"""
|
||||||
|
class_name = "MyComponent"
|
||||||
|
created_class = create_class(code, class_name)
|
||||||
|
instance = created_class()
|
||||||
|
result = instance.build()
|
||||||
|
assert result.value == "test"
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_class_with_multiple_external_classes():
|
||||||
|
code = """
|
||||||
|
from langflow.custom import CustomComponent
|
||||||
|
|
||||||
|
class ExternalClass1:
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
class ExternalClass2:
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
class MyComponent(CustomComponent):
|
||||||
|
def build(self):
|
||||||
|
return ExternalClass1("test1"), ExternalClass2("test2")
|
||||||
|
"""
|
||||||
|
class_name = "MyComponent"
|
||||||
|
created_class = create_class(code, class_name)
|
||||||
|
instance = created_class()
|
||||||
|
result1, result2 = instance.build()
|
||||||
|
assert result1.value == "test1"
|
||||||
|
assert result2.value == "test2"
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_class_with_external_variables_and_functions():
|
||||||
|
code = """
|
||||||
|
from langflow.custom import CustomComponent
|
||||||
|
|
||||||
|
external_variable = "external_value"
|
||||||
|
|
||||||
|
def external_function():
|
||||||
|
return "external_function_value"
|
||||||
|
|
||||||
|
class MyComponent(CustomComponent):
|
||||||
|
def build(self):
|
||||||
|
return external_variable, external_function()
|
||||||
|
"""
|
||||||
|
class_name = "MyComponent"
|
||||||
|
created_class = create_class(code, class_name)
|
||||||
|
instance = created_class()
|
||||||
|
result_variable, result_function = instance.build()
|
||||||
|
assert result_variable == "external_value"
|
||||||
|
assert result_function == "external_function_value"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue