From a8779b1205d1d9718cb6bfdda0a6722d0e0c35db Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 1 Apr 2024 00:13:33 -0300 Subject: [PATCH] Add PythonFunctionComponent and test case for it --- .../components/experimental/__init__.py | 1 + .../langflow/components/helpers/__init__.py | 2 +- tests/test_endpoints.py | 56 +++++++++++-------- tests/test_helper_components.py | 19 +------ tests/test_initial_setup.py | 7 +-- tests/text_experimental_components.py | 15 +++++ 6 files changed, 56 insertions(+), 44 deletions(-) create mode 100644 tests/text_experimental_components.py diff --git a/src/backend/base/langflow/components/experimental/__init__.py b/src/backend/base/langflow/components/experimental/__init__.py index 515667065..54ca60a3b 100644 --- a/src/backend/base/langflow/components/experimental/__init__.py +++ b/src/backend/base/langflow/components/experimental/__init__.py @@ -24,4 +24,5 @@ __all__ = [ "RunnableExecComponent", "SQLExecutorComponent", "SubFlowComponent", + "PythonFunctionComponent", ] diff --git a/src/backend/base/langflow/components/helpers/__init__.py b/src/backend/base/langflow/components/helpers/__init__.py index 5e06e9439..af3524d8e 100644 --- a/src/backend/base/langflow/components/helpers/__init__.py +++ b/src/backend/base/langflow/components/helpers/__init__.py @@ -4,13 +4,13 @@ from .DocumentToRecord import DocumentToRecordComponent from .IDGenerator import UUIDGeneratorComponent from .MessageHistory import MessageHistoryComponent from .UpdateRecord import UpdateRecordComponent +from .RecordsToText import RecordsToTextComponent __all__ = [ "Component", "UpdateRecordComponent", "DocumentToRecordComponent", "UUIDGeneratorComponent", - "PythonFunctionComponent", "RecordsToTextComponent", "CreateRecordComponent", "MessageHistoryComponent", diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py index 7b1ccd2ef..92ff35566 100644 --- a/tests/test_endpoints.py +++ b/tests/test_endpoints.py @@ -424,6 +424,19 @@ def test_build_vertex_invalid_vertex_id(client, added_flow_with_prompt_and_histo assert response.status_code == 500 +# Helper function to poll task status +def poll_task_status(client, headers, href, max_attempts=20, sleep_time=1): + for _ in range(max_attempts): + task_status_response = client.get( + href, + headers=headers, + ) + if task_status_response.status_code == 200 and task_status_response.json()["status"] == "SUCCESS": + return task_status_response.json() + time.sleep(sleep_time) + return None # Return None if task did not complete in time + + def test_successful_run(client, starter_project, created_api_key): headers = {"x-api-key": created_api_key.api_key} flow_id = starter_project["id"] @@ -455,11 +468,14 @@ def test_run_with_inputs_and_outputs(client, starter_project, created_api_key): headers = {"x-api-key": created_api_key.api_key} flow_id = starter_project["id"] payload = { - "inputs": [{"components": ["component1"], "input_value": "value1"}], - "outputs": ["Component Name", "component_id"], + "input_value": "value1", + "input_type": "text", + "output_type": "text", + "tweaks": {"parameter_name": "value"}, + "stream": False, } response = client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers) - assert response.status_code == status.HTTP_200_OK + assert response.status_code == status.HTTP_200_OK, response.text # Validate the response structure and content @@ -475,16 +491,10 @@ def test_run_flow_with_caching_success(client: TestClient, starter_project, crea flow_id = starter_project["id"] headers = {"x-api-key": created_api_key.api_key} payload = { - "inputs": [ - {"components": ["component1"], "input_value": "value1"}, - {"components": ["component3"], "input_value": "value2"}, - ], - "outputs": ["Component Name", "component_id"], - "tweaks": { - "parameter_name": "value", - "Component Name": {"parameter_name": "value"}, - "component_id": {"parameter_name": "value"}, - }, + "input_value": "value1", + "input_type": "text", + "output_type": "text", + "tweaks": {"parameter_name": "value"}, "stream": False, } response = client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers) @@ -497,7 +507,7 @@ def test_run_flow_with_caching_success(client: TestClient, starter_project, crea def test_run_flow_with_caching_invalid_flow_id(client: TestClient, created_api_key): invalid_flow_id = uuid4() headers = {"x-api-key": created_api_key.api_key} - payload = {"inputs": [], "outputs": [], "tweaks": {}, "stream": False} + payload = {"input_value": "", "input_type": "text", "output_type": "text", "tweaks": {}, "stream": False} response = client.post(f"/api/v1/run/{invalid_flow_id}", json=payload, headers=headers) assert response.status_code == status.HTTP_404_NOT_FOUND data = response.json() @@ -508,8 +518,7 @@ def test_run_flow_with_caching_invalid_flow_id(client: TestClient, created_api_k def test_run_flow_with_caching_invalid_input_format(client: TestClient, starter_project, created_api_key): flow_id = starter_project["id"] headers = {"x-api-key": created_api_key.api_key} - payload = {"inputs": [{"invalid_key": "value"}], "outputs": [], "tweaks": {}, "stream": False} - # This should raise an http 422 error not validation error + payload = {"invalid_key": "value"} response = client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY @@ -518,8 +527,9 @@ def test_run_flow_with_session_id(client, starter_project, created_api_key): headers = {"x-api-key": created_api_key.api_key} flow_id = starter_project["id"] payload = { - "inputs": [{"components": ["component1"], "input_value": "value1"}], - "outputs": ["Component Name", "component_id"], + "input_value": "value1", + "input_type": "text", + "output_type": "text", "session_id": "test-session-id", } response = client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers) @@ -534,8 +544,9 @@ def test_run_flow_with_invalid_session_id(client, starter_project, created_api_k headers = {"x-api-key": created_api_key.api_key} flow_id = starter_project["id"] payload = { - "inputs": [{"components": ["component1"], "input_value": "value1"}], - "outputs": ["Component Name", "component_id"], + "input_value": "value1", + "input_type": "text", + "output_type": "text", "session_id": "invalid-session-id", } response = client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers) @@ -549,8 +560,9 @@ def test_run_flow_with_invalid_tweaks(client, starter_project, created_api_key): headers = {"x-api-key": created_api_key.api_key} flow_id = starter_project["id"] payload = { - "inputs": [{"components": ["component1"], "input_value": "value1"}], - "outputs": ["Component Name", "component_id"], + "input_value": "value1", + "input_type": "text", + "output_type": "text", "tweaks": {"invalid_tweak": "value"}, } response = client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers) diff --git a/tests/test_helper_components.py b/tests/test_helper_components.py index fcbb3b3f3..bae20a27a 100644 --- a/tests/test_helper_components.py +++ b/tests/test_helper_components.py @@ -1,5 +1,4 @@ from langchain_core.documents import Document - from langflow.components import helpers from langflow.interface.custom.utils import build_custom_component_template from langflow.schema import Record @@ -52,23 +51,9 @@ def test_uuid_generator_component(): assert len(result) == 36 -def test_python_function_component(): - # Arrange - python_function_component = helpers.PythonFunctionComponent() - - # Act - # function must be a string representation - function = "def function():\n return 'Hello, World!'" - # result is the callable function - result = python_function_component.build(function) - - # Assert - assert result() == "Hello, World!" - - def test_records_as_text_component(): # Arrange - records_as_text_component = helpers.RecordsAsTextComponent() + records_as_text_component = helpers.RecordsToTextComponent() # Act # Replace with your actual test data @@ -83,7 +68,7 @@ def test_records_as_text_component(): def test_text_to_record_component(): # Arrange - text_to_record_component = helpers.TextToRecordComponent() + text_to_record_component = helpers.CreateRecordComponent() # Act # Replace with your actual test data diff --git a/tests/test_initial_setup.py b/tests/test_initial_setup.py index d60d4951a..545d623bc 100644 --- a/tests/test_initial_setup.py +++ b/tests/test_initial_setup.py @@ -1,9 +1,6 @@ from itertools import chain import pytest -from sqlalchemy import func -from sqlmodel import select - from langflow.graph.graph.base import Graph from langflow.graph.schema import ResultData from langflow.initial_setup.setup import ( @@ -14,6 +11,8 @@ from langflow.initial_setup.setup import ( ) from langflow.services.database.models.flow.model import Flow from langflow.services.deps import session_scope +from sqlalchemy import func +from sqlmodel import select def test_load_starter_projects(): @@ -26,7 +25,7 @@ def test_get_project_data(): projects = load_starter_projects() for project in projects: data = get_project_data(project) - assert all(d is not None for d in data) + assert all(d is not None for d in data), f"Project {project} data is None" def test_create_or_update_starter_projects(client): diff --git a/tests/text_experimental_components.py b/tests/text_experimental_components.py new file mode 100644 index 000000000..a4ee47729 --- /dev/null +++ b/tests/text_experimental_components.py @@ -0,0 +1,15 @@ +from langflow.components import experimental + + +def test_python_function_component(): + # Arrange + python_function_component = experimental.PythonFunctionComponent() + + # Act + # function must be a string representation + function = "def function():\n return 'Hello, World!'" + # result is the callable function + result = python_function_component.build(function) + + # Assert + assert result() == "Hello, World!"