From ccba14bec4bbd5ebeb122b7993ce8dbf63129b32 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 29 Oct 2024 10:45:38 -0300 Subject: [PATCH] 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> --- .devcontainer/devcontainer.json | 3 + localhost-19-2.db | Bin 0 -> 4096 bytes src/backend/base/langflow/utils/validate.py | 10 +++ src/backend/tests/unit/test_validate_code.py | 72 ++++++++++++++++++- 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 localhost-19-2.db diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4df8df96b..31591f735 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -34,6 +34,9 @@ "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. diff --git a/localhost-19-2.db b/localhost-19-2.db new file mode 100644 index 0000000000000000000000000000000000000000..7ee7c113a09428e4daafacb6e70a35d18573e608 GIT binary patch literal 4096 zcmWFz^vNtqRY=P(%1ta$FlG>7U}9o$P*7lCU|@t|AVoG{WYDWB;00+HAlr;ljiVtj n8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O6ovo*4{!$i literal 0 HcmV?d00001 diff --git a/src/backend/base/langflow/utils/validate.py b/src/backend/base/langflow/utils/validate.py index 5178cf735..4de5f9884 100644 --- a/src/backend/base/langflow/utils/validate.py +++ b/src/backend/base/langflow/utils/validate.py @@ -227,6 +227,16 @@ def prepare_global_scope(code, module): except ModuleNotFoundError as e: msg = f"Module {node.module} not found. Please install it and try again" 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=[]), "", "exec") + exec(class_code, exec_globals) + elif isinstance(node, ast.FunctionDef): + function_code = compile(ast.Module(body=[node], type_ignores=[]), "", "exec") + exec(function_code, exec_globals) + elif isinstance(node, ast.Assign): + assign_code = compile(ast.Module(body=[node], type_ignores=[]), "", "exec") + exec(assign_code, exec_globals) return exec_globals diff --git a/src/backend/tests/unit/test_validate_code.py b/src/backend/tests/unit/test_validate_code.py index 93b567cd9..4470b3efe 100644 --- a/src/backend/tests/unit/test_validate_code.py +++ b/src/backend/tests/unit/test_validate_code.py @@ -2,7 +2,13 @@ from pathlib import Path from unittest import mock 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 @@ -100,3 +106,67 @@ def my_function(x): """ with mock.patch("requests.get", side_effect=MissingSchema), pytest.raises(MissingSchema): 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"