feat(utils): add code hash generation and module name in Custom Components (#9107)
* refactor: update _generate_code_hash function and enhance module name handling - Removed the class_name parameter from _generate_code_hash for improved clarity and simplicity. - Added a new function, get_module_name_from_display_name, to generate module names from display names in snake_case. - Updated build_custom_component_template_from_inputs to use the new module name generation logic when module_name is None. - Enhanced error handling in code hash generation to log exceptions appropriately. - Updated unit tests to reflect changes in the _generate_code_hash function and to verify the new module name generation functionality. * fix: enhance module name handling and error logging in build_custom_component_template - Added logic to derive module names from display names when not provided, improving metadata accuracy. - Refined error handling for code hash generation, ensuring exceptions are logged appropriately for better debugging. * test: add comprehensive unit tests for metadata generation in custom components - Introduced multiple tests to ensure that the `build_custom_component_template` function consistently generates metadata, including module names and code hashes, across various scenarios. - Verified that metadata is correctly returned when module names are provided or omitted, and that code hashes change with component code modifications. - Included tests for handling unicode characters in component code to ensure robustness in metadata generation. * test: update unit tests to use Component class for metadata generation - Refactored test cases to replace the CustomComponent with the new Component class, ensuring consistency in testing metadata addition in template builders. - Adjusted mock component attributes to align with the updated class structure, enhancing clarity and maintainability of the tests. * test: add unit tests for custom component metadata retrieval and consistency - Introduced new tests for the /custom_component endpoint to verify that it returns accurate metadata, including module names and code hashes. - Ensured that identical component code produces consistent metadata across multiple requests, enhancing the reliability of the custom component functionality. * refactor: improve error logging in code hash generation - Updated error logging in `build_custom_component_template_from_inputs` and `build_custom_component_template` to use debug level with exception context, enhancing clarity for debugging while reducing log noise. - This change aims to provide more detailed insights during error occurrences without cluttering the error logs.
This commit is contained in:
parent
cfb29134bb
commit
2c405f77e7
4 changed files with 437 additions and 21 deletions
|
|
@ -33,13 +33,12 @@ from langflow.utils import validate
|
|||
from langflow.utils.util import get_base_classes
|
||||
|
||||
|
||||
def _generate_code_hash(source_code: str, modname: str, class_name: str) -> str:
|
||||
def _generate_code_hash(source_code: str, modname: str) -> str:
|
||||
"""Generate a hash of the component source code.
|
||||
|
||||
Args:
|
||||
source_code: The source code string
|
||||
modname: The module name for context
|
||||
class_name: The class name for context
|
||||
|
||||
Returns:
|
||||
SHA256 hash of the source code
|
||||
|
|
@ -50,7 +49,7 @@ def _generate_code_hash(source_code: str, modname: str, class_name: str) -> str:
|
|||
TypeError: If source_code is not a string
|
||||
"""
|
||||
if not source_code:
|
||||
msg = f"Empty source code for {class_name} in {modname}"
|
||||
msg = f"Empty source code for {modname}"
|
||||
raise ValueError(msg)
|
||||
|
||||
# Generate SHA256 hash of the source code
|
||||
|
|
@ -439,6 +438,18 @@ def add_code_field_to_build_config(build_config: dict, raw_code: str):
|
|||
return build_config
|
||||
|
||||
|
||||
def get_module_name_from_display_name(display_name: str):
|
||||
"""Get the module name from the display name."""
|
||||
# Convert display name to snake_case for Python module name
|
||||
# e.g., "Custom Component" -> "custom_component"
|
||||
# Remove extra spaces and convert to lowercase
|
||||
cleaned_name = re.sub(r"\s+", " ", display_name.strip())
|
||||
# Replace spaces with underscores and convert to lowercase
|
||||
module_name = cleaned_name.replace(" ", "_").lower()
|
||||
# Remove any non-alphanumeric characters except underscores
|
||||
return re.sub(r"[^a-z0-9_]", "", module_name)
|
||||
|
||||
|
||||
def build_custom_component_template_from_inputs(
|
||||
custom_component: Component | CustomComponent, user_id: str | UUID | None = None, module_name: str | None = None
|
||||
):
|
||||
|
|
@ -479,11 +490,17 @@ def build_custom_component_template_from_inputs(
|
|||
reorder_fields(frontend_node, cc_instance._get_field_order())
|
||||
if module_name:
|
||||
frontend_node.metadata["module"] = module_name
|
||||
else:
|
||||
module_name = get_module_name_from_display_name(frontend_node.display_name)
|
||||
frontend_node.metadata["module"] = f"custom_components.{module_name}"
|
||||
|
||||
# Generate code hash for cache invalidation and debugging
|
||||
code_hash = _generate_code_hash(custom_component._code, module_name, ctype_name)
|
||||
# Generate code hash for cache invalidation and debugging
|
||||
try:
|
||||
code_hash = _generate_code_hash(custom_component._code, module_name)
|
||||
if code_hash:
|
||||
frontend_node.metadata["code_hash"] = code_hash
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.opt(exception=exc).debug(f"Error generating code hash for {custom_component.__class__.__name__}")
|
||||
|
||||
return frontend_node.to_dict(keep_name=False), cc_instance
|
||||
|
||||
|
|
@ -546,11 +563,17 @@ def build_custom_component_template(
|
|||
|
||||
if module_name:
|
||||
frontend_node.metadata["module"] = module_name
|
||||
else:
|
||||
module_name = get_module_name_from_display_name(frontend_node.display_name)
|
||||
frontend_node.metadata["module"] = f"custom_components.{module_name}"
|
||||
|
||||
# Generate code hash for cache invalidation and debugging
|
||||
code_hash = _generate_code_hash(custom_component._code, module_name, custom_component.__class__.__name__)
|
||||
# Generate code hash for cache invalidation and debugging
|
||||
try:
|
||||
code_hash = _generate_code_hash(custom_component._code, module_name)
|
||||
if code_hash:
|
||||
frontend_node.metadata["code_hash"] = code_hash
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.opt(exception=exc).debug(f"Error generating code hash for {custom_component.__class__.__name__}")
|
||||
|
||||
return frontend_node.to_dict(keep_name=False), custom_instance
|
||||
except Exception as exc:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from typing import Any
|
|||
from anyio import Path
|
||||
from fastapi import status
|
||||
from httpx import AsyncClient
|
||||
from langflow.api.v1.schemas import UpdateCustomComponentRequest
|
||||
from langflow.api.v1.schemas import CustomComponentRequest, UpdateCustomComponentRequest
|
||||
from langflow.components.agents.agent import AgentComponent
|
||||
from langflow.custom.utils import build_custom_component_template
|
||||
|
||||
|
|
@ -106,3 +106,89 @@ async def test_update_component_model_name_options(client: AsyncClient, logged_i
|
|||
assert response.status_code == status.HTTP_200_OK
|
||||
assert "template" in result
|
||||
assert "model_name" not in result["template"]
|
||||
|
||||
|
||||
async def test_custom_component_endpoint_returns_metadata(client: AsyncClient, logged_in_headers: dict):
|
||||
"""Test that the /custom_component endpoint returns metadata with module and code_hash."""
|
||||
component_code = """
|
||||
from langflow.custom import Component
|
||||
from langflow.inputs.inputs import MessageTextInput
|
||||
from langflow.template.field.base import Output
|
||||
|
||||
class TestMetadataComponent(Component):
|
||||
display_name = "Test Metadata Component"
|
||||
description = "Test component for metadata"
|
||||
|
||||
inputs = [
|
||||
MessageTextInput(display_name="Input", name="input_value"),
|
||||
]
|
||||
outputs = [
|
||||
Output(display_name="Output", name="output", method="process_input"),
|
||||
]
|
||||
|
||||
def process_input(self) -> str:
|
||||
return f"Processed: {self.input_value}"
|
||||
"""
|
||||
|
||||
request = CustomComponentRequest(code=component_code)
|
||||
response = await client.post("api/v1/custom_component", json=request.model_dump(), headers=logged_in_headers)
|
||||
result = response.json()
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert "data" in result
|
||||
assert "type" in result
|
||||
|
||||
# Verify metadata is present in the response
|
||||
frontend_node = result["data"]
|
||||
assert "metadata" in frontend_node, "Frontend node should contain metadata"
|
||||
|
||||
metadata = frontend_node["metadata"]
|
||||
assert "module" in metadata, "Metadata should contain module field"
|
||||
assert "code_hash" in metadata, "Metadata should contain code_hash field"
|
||||
|
||||
# Verify metadata values
|
||||
assert isinstance(metadata["module"], str), "Module should be a string"
|
||||
expected_module = "custom_components.test_metadata_component"
|
||||
assert metadata["module"] == expected_module, "Module should be auto-generated from display_name"
|
||||
|
||||
assert isinstance(metadata["code_hash"], str), "Code hash should be a string"
|
||||
assert len(metadata["code_hash"]) == 12, "Code hash should be 12 characters long"
|
||||
assert all(c in "0123456789abcdef" for c in metadata["code_hash"]), "Code hash should be hexadecimal"
|
||||
|
||||
|
||||
async def test_custom_component_endpoint_metadata_consistency(client: AsyncClient, logged_in_headers: dict):
|
||||
"""Test that the same component code produces consistent metadata."""
|
||||
component_code = """
|
||||
from langflow.custom import Component
|
||||
from langflow.template.field.base import Output
|
||||
|
||||
class ConsistencyTestComponent(Component):
|
||||
display_name = "Consistency Test"
|
||||
|
||||
outputs = [
|
||||
Output(display_name="Output", name="output", method="get_result"),
|
||||
]
|
||||
|
||||
def get_result(self) -> str:
|
||||
return "consistent result"
|
||||
"""
|
||||
|
||||
# Make two identical requests
|
||||
request = CustomComponentRequest(code=component_code)
|
||||
|
||||
response1 = await client.post("api/v1/custom_component", json=request.model_dump(), headers=logged_in_headers)
|
||||
result1 = response1.json()
|
||||
|
||||
response2 = await client.post("api/v1/custom_component", json=request.model_dump(), headers=logged_in_headers)
|
||||
result2 = response2.json()
|
||||
|
||||
# Both requests should succeed
|
||||
assert response1.status_code == status.HTTP_200_OK
|
||||
assert response2.status_code == status.HTTP_200_OK
|
||||
|
||||
# Metadata should be identical
|
||||
metadata1 = result1["data"]["metadata"]
|
||||
metadata2 = result2["data"]["metadata"]
|
||||
|
||||
assert metadata1["module"] == metadata2["module"], "Module names should be consistent"
|
||||
assert metadata1["code_hash"] == metadata2["code_hash"], "Code hashes should be consistent for identical code"
|
||||
|
|
|
|||
|
|
@ -13,9 +13,8 @@ class TestCodeHashGeneration:
|
|||
"""Test basic hash generation."""
|
||||
source = "def test(): pass"
|
||||
modname = "test_module"
|
||||
class_name = "TestClass"
|
||||
|
||||
result = _generate_code_hash(source, modname, class_name)
|
||||
result = _generate_code_hash(source, modname)
|
||||
|
||||
assert isinstance(result, str)
|
||||
assert len(result) == 12
|
||||
|
|
@ -24,24 +23,24 @@ class TestCodeHashGeneration:
|
|||
def test_hash_empty_source_raises(self):
|
||||
"""Test that empty source raises ValueError."""
|
||||
with pytest.raises(ValueError, match="Empty source code"):
|
||||
_generate_code_hash("", "mod", "cls")
|
||||
_generate_code_hash("", "mod")
|
||||
|
||||
def test_hash_none_source_raises(self):
|
||||
"""Test that None source raises ValueError."""
|
||||
with pytest.raises(ValueError, match="Empty source code"):
|
||||
_generate_code_hash(None, "mod", "cls")
|
||||
_generate_code_hash(None, "mod")
|
||||
|
||||
def test_hash_consistency(self):
|
||||
"""Test that same code produces same hash."""
|
||||
source = "class A: pass"
|
||||
hash1 = _generate_code_hash(source, "mod", "A")
|
||||
hash2 = _generate_code_hash(source, "mod", "A")
|
||||
hash1 = _generate_code_hash(source, "mod")
|
||||
hash2 = _generate_code_hash(source, "mod")
|
||||
assert hash1 == hash2
|
||||
|
||||
def test_hash_different_code(self):
|
||||
"""Test that different code produces different hash."""
|
||||
hash1 = _generate_code_hash("class A: pass", "mod", "A")
|
||||
hash2 = _generate_code_hash("class B: pass", "mod", "B")
|
||||
hash1 = _generate_code_hash("class A: pass", "mod")
|
||||
hash2 = _generate_code_hash("class B: pass", "mod")
|
||||
assert hash1 != hash2
|
||||
|
||||
|
||||
|
|
@ -61,6 +60,7 @@ class TestMetadataInTemplateBuilders:
|
|||
mock_frontend.to_dict = Mock(return_value={"test": "data"})
|
||||
mock_frontend.validate_component = Mock()
|
||||
mock_frontend.set_base_classes_from_outputs = Mock()
|
||||
mock_frontend.display_name = "Test Component"
|
||||
mock_frontend_class.from_inputs.return_value = mock_frontend
|
||||
|
||||
# Create test component
|
||||
|
|
@ -93,7 +93,7 @@ class TestMetadataInTemplateBuilders:
|
|||
@patch("langflow.custom.utils.CustomComponentFrontendNode")
|
||||
def test_build_template_adds_metadata_with_module(self, mock_frontend_class):
|
||||
"""Test that build_custom_component_template adds metadata when module_name is provided."""
|
||||
from langflow.custom.custom_component.custom_component import CustomComponent
|
||||
from langflow.custom.custom_component.component import Component
|
||||
from langflow.custom.utils import build_custom_component_template
|
||||
|
||||
# Setup mock frontend node
|
||||
|
|
@ -103,9 +103,9 @@ class TestMetadataInTemplateBuilders:
|
|||
mock_frontend_class.return_value = mock_frontend
|
||||
|
||||
# Create test component
|
||||
test_component = Mock(spec=CustomComponent)
|
||||
test_component.__class__.__name__ = "CustomTestComponent"
|
||||
test_component._code = "class CustomTestComponent: pass"
|
||||
test_component = Mock(spec=Component)
|
||||
test_component.__class__.__name__ = "TestComponent"
|
||||
test_component._code = "class TestComponent: pass"
|
||||
test_component.template_config = {"display_name": "Test"}
|
||||
test_component.get_function_entrypoint_args = []
|
||||
test_component._get_function_entrypoint_return_type = []
|
||||
|
|
@ -135,8 +135,51 @@ class TestMetadataInTemplateBuilders:
|
|||
def test_hash_generation_unicode(self):
|
||||
"""Test hash generation with unicode characters."""
|
||||
source = "# Test with unicode: 你好 🌟\nclass Component: pass"
|
||||
result = _generate_code_hash(source, "unicode_mod", "Component")
|
||||
result = _generate_code_hash(source, "unicode_mod")
|
||||
|
||||
assert isinstance(result, str)
|
||||
assert len(result) == 12
|
||||
assert all(c in "0123456789abcdef" for c in result)
|
||||
|
||||
@patch("langflow.custom.utils.ComponentFrontendNode")
|
||||
def test_build_from_inputs_without_module_generates_default(self, mock_frontend_class):
|
||||
"""Test that build_custom_component_template_from_inputs generates default module when module_name is None."""
|
||||
from langflow.custom.custom_component.component import Component
|
||||
from langflow.custom.utils import build_custom_component_template_from_inputs
|
||||
|
||||
# Setup mock frontend node
|
||||
mock_frontend = Mock()
|
||||
mock_frontend.metadata = {}
|
||||
mock_frontend.outputs = []
|
||||
mock_frontend.to_dict = Mock(return_value={"test": "data"})
|
||||
mock_frontend.validate_component = Mock()
|
||||
mock_frontend.set_base_classes_from_outputs = Mock()
|
||||
mock_frontend.display_name = "My Test Component"
|
||||
mock_frontend_class.from_inputs.return_value = mock_frontend
|
||||
|
||||
# Create test component
|
||||
test_component = Mock(spec=Component)
|
||||
test_component.__class__.__name__ = "TestComponent"
|
||||
test_component._code = "class TestComponent: pass"
|
||||
test_component.template_config = {"inputs": []}
|
||||
|
||||
# Mock get_component_instance to return a mock instance
|
||||
with patch("langflow.custom.utils.get_component_instance") as mock_get_instance:
|
||||
mock_instance = Mock()
|
||||
mock_instance.get_template_config = Mock(return_value={})
|
||||
mock_instance._get_field_order = Mock(return_value=[])
|
||||
mock_get_instance.return_value = mock_instance
|
||||
|
||||
# Mock add_code_field to return the frontend node
|
||||
with (
|
||||
patch("langflow.custom.utils.add_code_field", return_value=mock_frontend),
|
||||
patch("langflow.custom.utils.reorder_fields"),
|
||||
):
|
||||
# Call the function without module_name
|
||||
template, _ = build_custom_component_template_from_inputs(test_component, module_name=None)
|
||||
|
||||
# Verify metadata was added with generated module name
|
||||
assert "module" in mock_frontend.metadata
|
||||
assert mock_frontend.metadata["module"] == "custom_components.my_test_component"
|
||||
assert "code_hash" in mock_frontend.metadata
|
||||
assert len(mock_frontend.metadata["code_hash"]) == 12
|
||||
|
|
|
|||
|
|
@ -433,3 +433,267 @@ def test_custom_component_subclass_from_lctoolcomponent():
|
|||
assert "outputs" in frontend_node
|
||||
assert frontend_node["outputs"][0]["types"] != []
|
||||
assert frontend_node["outputs"][1]["types"] != []
|
||||
|
||||
|
||||
def test_build_custom_component_template_includes_metadata_with_module():
|
||||
"""Test that build_custom_component_template includes metadata when module_name is provided."""
|
||||
code = dedent("""
|
||||
from langflow.custom import Component
|
||||
from langflow.inputs.inputs import MessageTextInput
|
||||
from langflow.template.field.base import Output
|
||||
|
||||
class TestMetadataComponent(Component):
|
||||
display_name = "Test Metadata Component"
|
||||
description = "Test component for metadata"
|
||||
|
||||
inputs = [
|
||||
MessageTextInput(display_name="Input", name="input_value"),
|
||||
]
|
||||
outputs = [
|
||||
Output(display_name="Output", name="output", method="process_input"),
|
||||
]
|
||||
|
||||
def process_input(self) -> str:
|
||||
return f"Processed: {self.input_value}"
|
||||
""")
|
||||
|
||||
component = Component(_code=code)
|
||||
frontend_node, _ = build_custom_component_template(component, module_name="test.module")
|
||||
|
||||
# Verify metadata is present
|
||||
assert "metadata" in frontend_node
|
||||
metadata = frontend_node["metadata"]
|
||||
|
||||
# Verify metadata contains required fields
|
||||
assert "module" in metadata
|
||||
assert "code_hash" in metadata
|
||||
|
||||
# Verify metadata values
|
||||
assert metadata["module"] == "test.module"
|
||||
assert isinstance(metadata["code_hash"], str)
|
||||
assert len(metadata["code_hash"]) == 12
|
||||
assert all(c in "0123456789abcdef" for c in metadata["code_hash"])
|
||||
|
||||
|
||||
def test_build_custom_component_template_always_has_metadata():
|
||||
"""Test that build_custom_component_template always generates metadata, even when module_name is None."""
|
||||
code = dedent("""
|
||||
from langflow.custom import Component
|
||||
from langflow.template.field.base import Output
|
||||
|
||||
class TestAlwaysMetadata(Component):
|
||||
display_name = "Test Always Metadata"
|
||||
|
||||
outputs = [
|
||||
Output(display_name="Output", name="output", method="get_result"),
|
||||
]
|
||||
|
||||
def get_result(self) -> str:
|
||||
return "test"
|
||||
""")
|
||||
|
||||
component = Component(_code=code)
|
||||
frontend_node, _ = build_custom_component_template(component, module_name=None)
|
||||
|
||||
# Metadata should ALWAYS be present
|
||||
assert "metadata" in frontend_node
|
||||
metadata = frontend_node["metadata"]
|
||||
|
||||
assert "module" in metadata
|
||||
assert "code_hash" in metadata
|
||||
|
||||
# Should generate default module name from display_name
|
||||
assert metadata["module"] == "custom_components.test_always_metadata"
|
||||
assert len(metadata["code_hash"]) == 12
|
||||
|
||||
|
||||
def test_build_custom_component_template_metadata_hash_changes():
|
||||
"""Test that code hash changes when component code changes."""
|
||||
code_v1 = dedent("""
|
||||
from langflow.custom import Component
|
||||
from langflow.template.field.base import Output
|
||||
|
||||
class VersionComponent(Component):
|
||||
display_name = "Version Component"
|
||||
version = "1.0"
|
||||
|
||||
outputs = [
|
||||
Output(display_name="Output", name="output", method="get_version"),
|
||||
]
|
||||
|
||||
def get_version(self) -> str:
|
||||
return "version 1.0"
|
||||
""")
|
||||
|
||||
code_v2 = dedent("""
|
||||
from langflow.custom import Component
|
||||
from langflow.template.field.base import Output
|
||||
|
||||
class VersionComponent(Component):
|
||||
display_name = "Version Component"
|
||||
version = "2.0"
|
||||
|
||||
outputs = [
|
||||
Output(display_name="Output", name="output", method="get_version"),
|
||||
]
|
||||
|
||||
def get_version(self) -> str:
|
||||
return "version 2.0"
|
||||
""")
|
||||
|
||||
component_v1 = Component(_code=code_v1)
|
||||
component_v2 = Component(_code=code_v2)
|
||||
|
||||
frontend_node_v1, _ = build_custom_component_template(component_v1, module_name="test.version")
|
||||
frontend_node_v2, _ = build_custom_component_template(component_v2, module_name="test.version")
|
||||
|
||||
metadata_v1 = frontend_node_v1["metadata"]
|
||||
metadata_v2 = frontend_node_v2["metadata"]
|
||||
|
||||
# Same module name
|
||||
assert metadata_v1["module"] == metadata_v2["module"]
|
||||
|
||||
# Different code hashes
|
||||
assert metadata_v1["code_hash"] != metadata_v2["code_hash"]
|
||||
|
||||
|
||||
def test_build_custom_component_template_metadata_unicode():
|
||||
"""Test that metadata generation works with unicode characters in code."""
|
||||
code = dedent("""
|
||||
from langflow.custom import Component
|
||||
from langflow.template.field.base import Output
|
||||
|
||||
class UnicodeComponent(Component):
|
||||
display_name = "Unicode Test 🌟"
|
||||
description = "测试组件 with émojis"
|
||||
|
||||
outputs = [
|
||||
Output(display_name="Output", name="output", method="get_unicode"),
|
||||
]
|
||||
|
||||
def get_unicode(self) -> str:
|
||||
# Comment with unicode: 你好世界 🚀
|
||||
return "Hello 世界!"
|
||||
""")
|
||||
|
||||
component = Component(_code=code)
|
||||
frontend_node, _ = build_custom_component_template(component, module_name="unicode.test")
|
||||
|
||||
# Verify metadata is present and valid
|
||||
metadata = frontend_node["metadata"]
|
||||
assert "module" in metadata
|
||||
assert "code_hash" in metadata
|
||||
|
||||
# Verify hash is valid hexadecimal
|
||||
code_hash = metadata["code_hash"]
|
||||
assert len(code_hash) == 12
|
||||
assert all(c in "0123456789abcdef" for c in code_hash)
|
||||
|
||||
|
||||
def test_build_custom_component_template_component_always_has_metadata():
|
||||
"""Test that build_custom_component_template always returns metadata for Component path."""
|
||||
code = dedent("""
|
||||
from langflow.custom import Component
|
||||
from langflow.inputs.inputs import MessageTextInput
|
||||
from langflow.template.field.base import Output
|
||||
|
||||
class TestComponentMetadata(Component):
|
||||
display_name = "Test Component Metadata"
|
||||
|
||||
inputs = [
|
||||
MessageTextInput(display_name="Input", name="input_value"),
|
||||
]
|
||||
outputs = [
|
||||
Output(display_name="Output", name="output", method="process_input"),
|
||||
]
|
||||
|
||||
def process_input(self) -> str:
|
||||
return f"Processed: {self.input_value}"
|
||||
""")
|
||||
|
||||
component = Component(_code=code)
|
||||
frontend_node, _ = build_custom_component_template(component, module_name=None)
|
||||
|
||||
# Metadata should ALWAYS be present, even for Component without module_name
|
||||
assert "metadata" in frontend_node
|
||||
metadata = frontend_node["metadata"]
|
||||
|
||||
assert "module" in metadata
|
||||
assert "code_hash" in metadata
|
||||
|
||||
# Should generate default module name from display_name
|
||||
assert metadata["module"] == "custom_components.test_component_metadata"
|
||||
assert len(metadata["code_hash"]) == 12
|
||||
|
||||
|
||||
def test_metadata_always_returned_comprehensive():
|
||||
"""Comprehensive test to verify metadata is ALWAYS returned in all scenarios."""
|
||||
# Test scenario 1: Component with module_name provided
|
||||
code1 = dedent("""
|
||||
from langflow.custom import Component
|
||||
from langflow.template.field.base import Output
|
||||
|
||||
class TestWithModule(Component):
|
||||
display_name = "Test With Module"
|
||||
|
||||
outputs = [
|
||||
Output(display_name="Output", name="output", method="get_result"),
|
||||
]
|
||||
|
||||
def get_result(self) -> str:
|
||||
return "with module"
|
||||
""")
|
||||
|
||||
component1 = Component(_code=code1)
|
||||
frontend_node1, _ = build_custom_component_template(component1, module_name="explicit.module")
|
||||
|
||||
assert "metadata" in frontend_node1
|
||||
assert frontend_node1["metadata"]["module"] == "explicit.module"
|
||||
assert "code_hash" in frontend_node1["metadata"]
|
||||
assert len(frontend_node1["metadata"]["code_hash"]) == 12
|
||||
|
||||
# Test scenario 2: Component without module_name (should generate default)
|
||||
component2 = Component(_code=code1)
|
||||
frontend_node2, _ = build_custom_component_template(component2, module_name=None)
|
||||
|
||||
assert "metadata" in frontend_node2
|
||||
assert frontend_node2["metadata"]["module"] == "custom_components.test_with_module"
|
||||
assert "code_hash" in frontend_node2["metadata"]
|
||||
assert len(frontend_node2["metadata"]["code_hash"]) == 12
|
||||
|
||||
# Test scenario 3: Component with inputs and outputs
|
||||
code3 = dedent("""
|
||||
from langflow.custom import Component
|
||||
from langflow.inputs.inputs import MessageTextInput
|
||||
from langflow.template.field.base import Output
|
||||
|
||||
class TestWithInputs(Component):
|
||||
display_name = "Test With Inputs"
|
||||
|
||||
inputs = [
|
||||
MessageTextInput(display_name="Input", name="input_value"),
|
||||
]
|
||||
outputs = [
|
||||
Output(display_name="Output", name="output", method="process_input"),
|
||||
]
|
||||
|
||||
def process_input(self) -> str:
|
||||
return f"Processed: {self.input_value}"
|
||||
""")
|
||||
|
||||
component3 = Component(_code=code3)
|
||||
frontend_node3, _ = build_custom_component_template(component3, module_name="custom.explicit")
|
||||
|
||||
assert "metadata" in frontend_node3
|
||||
assert frontend_node3["metadata"]["module"] == "custom.explicit"
|
||||
assert "code_hash" in frontend_node3["metadata"]
|
||||
assert len(frontend_node3["metadata"]["code_hash"]) == 12
|
||||
|
||||
# Test scenario 4: Component without module_name (should generate default)
|
||||
component4 = Component(_code=code3)
|
||||
frontend_node4, _ = build_custom_component_template(component4, module_name=None)
|
||||
|
||||
assert "metadata" in frontend_node4
|
||||
assert frontend_node4["metadata"]["module"] == "custom_components.test_with_inputs"
|
||||
assert "code_hash" in frontend_node4["metadata"]
|
||||
assert len(frontend_node4["metadata"]["code_hash"]) == 12
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue