fix: Make from_template_and_variables async for backwards compatibility, add sync version, add tests and small fixes (#4500)
* Refactor `trace_name` property to use `_id` instead of `_vertex.id` for component identification * Handle missing session_id attribute in component and agent classes * Add SUPPORTED_VERSIONS constant for version tracking in tests * Add utility to download components from GitHub in integration tests * Rename TestComponent to ComponentForTesting to avoid conflict with pytest * test: enhance PromptComponent tests for version support Add parameterized testing for supported versions and a validation for the latest PromptComponent. This improves test coverage and ensures compatibility across different versions. * refactor: move build_component_instance_for_tests utility to integration utils * Make `from_template_and_variables` async for backwards compatibility and add sync version * Refactor `PromptComponent` to use `Message.from_template` method across starter projects JSON files. * add await to `from_template_and_variables` call * Add async test for message prompt serialization and update cache directory handling - Introduced `async` in `test_message_prompt_serialization` for asynchronous message creation. - Added `test_message_sync_prompt_serialization` for synchronous message testing. - Updated cache directory paths to "langflow_test" for test isolation. - Utilized `monkeypatch` to set environment variable for cache directory in `langflow_cache_dir` fixture. * Add fixture and existence check in test_schema_message.py - Use `langflow_cache_dir` fixture in `test_message_with_single_image`. - Add assertion to verify the existence of `second_image`. * Add base test class to ensure file names are defined for all supported versions * Remove default value for 'file_name' parameter in 'build_component_instance_for_tests' function * Enhance `TestPromptComponent` with version-specific file name handling and base class integration * Refactor test_prompt_component_versions to use FILE_NAMES_MAPPING directly * Add component version tests and base classes for testing with/without client * Simplify `build_component_instance_for_tests` by returning `cc_class` directly * Refactor `TestPromptComponent` to use `ComponentTestBaseWithClient` and remove version tests * Add assertion for LANGFLOW_CONFIG_DIR in test_message_with_multiple_images * Refactor: update method call to `from_template` in `langchain_hub.py` * Handle missing '_id' attribute in 'trace_name' method of custom_component.py * Optimize `get_and_cache_all_types_dict` call by removing unnecessary thread usage in test.
This commit is contained in:
parent
8bb0f65b47
commit
768e6f02ba
21 changed files with 1013 additions and 32 deletions
39
src/backend/tests/base.py
Normal file
39
src/backend/tests/base.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.constants import SUPPORTED_VERSIONS
|
||||
from tests.integration.utils import build_component_instance_for_tests
|
||||
|
||||
# Sentinel value to mark undefined test cases
|
||||
DID_NOT_EXIST = object()
|
||||
|
||||
|
||||
class ComponentTestBase:
|
||||
component_class = None
|
||||
DEFAULT_KWARGS: dict[str, Any] = {}
|
||||
FILE_NAMES_MAPPING: dict[str, object | str] = {}
|
||||
|
||||
def test_all_versions_have_a_file_name_defined(self):
|
||||
for version in SUPPORTED_VERSIONS:
|
||||
assert version in self.FILE_NAMES_MAPPING
|
||||
assert self.FILE_NAMES_MAPPING[version] is not None
|
||||
|
||||
def test_component_versions(self):
|
||||
"""Test if the component works across different versions."""
|
||||
for version, file_name in self.FILE_NAMES_MAPPING.items():
|
||||
if file_name is DID_NOT_EXIST:
|
||||
continue
|
||||
|
||||
instance = build_component_instance_for_tests(version, file_name=file_name, **self.DEFAULT_KWARGS)
|
||||
result = instance()
|
||||
assert result is not None, f"{self.component_class.__name__} failed to execute in version {version}"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("client")
|
||||
class ComponentTestBaseWithClient:
|
||||
pass
|
||||
|
||||
|
||||
class ComponentTestBaseWithoutClient:
|
||||
pass
|
||||
1
src/backend/tests/constants.py
Normal file
1
src/backend/tests/constants.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
SUPPORTED_VERSIONS = ["1.0.15", "1.0.16", "1.0.17", "1.0.18", "1.0.19"]
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -7,6 +7,7 @@ import requests
|
|||
from astrapy.admin import parse_api_endpoint
|
||||
from langflow.api.v1.schemas import InputValueRequest
|
||||
from langflow.custom import Component
|
||||
from langflow.custom.eval import eval_custom_component_code
|
||||
from langflow.field_typing import Embeddings
|
||||
from langflow.graph import Graph
|
||||
from langflow.processing.process import run_graph_internal
|
||||
|
|
@ -105,6 +106,16 @@ def download_flow_from_github(name: str, version: str) -> JSONFlow:
|
|||
return JSONFlow(json=as_json)
|
||||
|
||||
|
||||
def download_component_from_github(module: str, file_name: str, version: str) -> Component:
|
||||
version_string = f"v{version}" if version != "main" else version
|
||||
response = requests.get(
|
||||
f"https://raw.githubusercontent.com/langflow-ai/langflow/{version_string}/src/backend/base/langflow/components/{module}/{file_name}.py",
|
||||
timeout=10,
|
||||
)
|
||||
response.raise_for_status()
|
||||
return Component(_code=response.text)
|
||||
|
||||
|
||||
async def run_json_flow(
|
||||
json_flow: JSONFlow, run_input: Any | None = None, session_id: str | None = None
|
||||
) -> dict[str, Any]:
|
||||
|
|
@ -170,3 +181,9 @@ async def run_single_component(
|
|||
graph, flow_id, session_id=session_id, inputs=graph_run_inputs, outputs=[component_id]
|
||||
)
|
||||
return graph.get_vertex(component_id).built_object
|
||||
|
||||
|
||||
def build_component_instance_for_tests(version: str, file_name: str, **kwargs):
|
||||
component = download_component_from_github("prompts", file_name, version)
|
||||
cc_class = eval_custom_component_code(component._code)
|
||||
return cc_class(**kwargs)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,30 @@
|
|||
import pytest
|
||||
from langflow.components.prompts import PromptComponent
|
||||
|
||||
from tests.base import ComponentTestBaseWithClient
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("client")
|
||||
class TestPromptComponent(ComponentTestBaseWithClient):
|
||||
component_class = PromptComponent
|
||||
DEFAULT_KWARGS = {"template": "Hello {name}!", "name": "John", "_session_id": "123"}
|
||||
FILE_NAMES_MAPPING = {
|
||||
"1.0.15": "Prompt",
|
||||
"1.0.16": "Prompt",
|
||||
"1.0.17": "Prompt",
|
||||
"1.0.18": "Prompt",
|
||||
"1.0.19": "Prompt",
|
||||
}
|
||||
|
||||
class TestPromptComponent:
|
||||
def test_post_code_processing(self):
|
||||
component = PromptComponent(template="Hello {name}!", name="John")
|
||||
component = self.component_class(**self.DEFAULT_KWARGS)
|
||||
frontend_node = component.to_frontend_node()
|
||||
node_data = frontend_node["data"]["node"]
|
||||
assert node_data["template"]["template"]["value"] == "Hello {name}!"
|
||||
assert "name" in node_data["custom_fields"]["template"]
|
||||
assert "name" in node_data["template"]
|
||||
assert node_data["template"]["name"]["value"] == "John"
|
||||
|
||||
def test_prompt_component_latest(self):
|
||||
result = PromptComponent(**self.DEFAULT_KWARGS)()
|
||||
assert result is not None
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ async def create_event_queue():
|
|||
return asyncio.Queue()
|
||||
|
||||
|
||||
class TestComponent(Component):
|
||||
class ComponentForTesting(Component):
|
||||
"""Test component that implements basic functionality."""
|
||||
|
||||
def build(self) -> None:
|
||||
|
|
@ -40,7 +40,7 @@ async def test_component_message_sending():
|
|||
event_manager = EventManager(queue)
|
||||
|
||||
# Create component
|
||||
component = TestComponent()
|
||||
component = ComponentForTesting()
|
||||
component.set_event_manager(event_manager)
|
||||
|
||||
# Create a message
|
||||
|
|
@ -68,7 +68,7 @@ async def test_component_tool_output():
|
|||
event_manager = EventManager(queue)
|
||||
|
||||
# Create component
|
||||
component = TestComponent()
|
||||
component = ComponentForTesting()
|
||||
component.set_event_manager(event_manager)
|
||||
|
||||
# Create a message with tool content
|
||||
|
|
@ -101,7 +101,7 @@ async def test_component_error_handling():
|
|||
event_manager = EventManager(queue)
|
||||
|
||||
# Create component
|
||||
component = TestComponent()
|
||||
component = ComponentForTesting()
|
||||
component.set_event_manager(event_manager)
|
||||
|
||||
# Trigger an error
|
||||
|
|
@ -133,7 +133,7 @@ async def test_component_build_results():
|
|||
event_manager = EventManager(queue)
|
||||
|
||||
# Create component
|
||||
component = TestComponent()
|
||||
component = ComponentForTesting()
|
||||
component.set_event_manager(event_manager)
|
||||
|
||||
# Add outputs to the component
|
||||
|
|
@ -165,7 +165,7 @@ async def test_component_logging():
|
|||
event_manager = EventManager(queue)
|
||||
|
||||
# Create component
|
||||
component = TestComponent()
|
||||
component = ComponentForTesting()
|
||||
component.set_event_manager(event_manager)
|
||||
|
||||
# Set current output (required for logging)
|
||||
|
|
@ -205,7 +205,7 @@ async def test_component_streaming_message():
|
|||
mock_graph.flow_id = "12345678-1234-5678-1234-567812345678" # Valid UUID string
|
||||
vertex.graph = mock_graph
|
||||
|
||||
component = TestComponent(_vertex=vertex)
|
||||
component = ComponentForTesting(_vertex=vertex)
|
||||
component.set_event_manager(event_manager)
|
||||
|
||||
# Create a chunk class that mimics LangChain's streaming format
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ def sample_image(langflow_cache_dir):
|
|||
|
||||
def test_message_prompt_serialization():
|
||||
template = "Hello, {name}!"
|
||||
message = Message.from_template_and_variables(template, name="Langflow")
|
||||
message = Message.from_template(template, name="Langflow")
|
||||
assert message.text == "Hello, Langflow!"
|
||||
|
||||
prompt = message.load_lc_prompt()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue