Skip to content
Open
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1572154
feat: add FunctionComponent and InputConfig for dynamic function wrap…
ogabrielluiz Nov 30, 2025
4bf1a1a
test: add unit tests for FunctionComponent functionality and behavior
ogabrielluiz Nov 30, 2025
3932cd4
feat: add tests for auto-wrapping functions in Component.set()
ogabrielluiz Nov 30, 2025
c536920
feat: add functionality to wrap plain callables as FunctionComponents
ogabrielluiz Nov 30, 2025
38d2c4f
feat: implement FunctionComponent class for dynamic function wrapping
ogabrielluiz Nov 30, 2025
21e6d57
feat: enhance eval_custom_component_code to support pure function com…
ogabrielluiz Nov 30, 2025
ad62df5
feat: update output type handling in FunctionComponent to default to …
ogabrielluiz Nov 30, 2025
2a19cfe
feat: add integration tests for FunctionComponent execution and seria…
ogabrielluiz Nov 30, 2025
a158e65
feat: update output type handling in FunctionComponent tests and add …
ogabrielluiz Nov 30, 2025
0a231f1
feat: add comprehensive tests for eval_custom_component_code handling…
ogabrielluiz Nov 30, 2025
908dbb2
feat: add edge case tests for FunctionComponent handling of unusual f…
ogabrielluiz Nov 30, 2025
1d6d65c
feat: enhance validate_code to support detection of function and clas…
ogabrielluiz Nov 30, 2025
2b85c47
feat: enhance _create_function_component_class to handle multiple fun…
ogabrielluiz Nov 30, 2025
ee503be
feat: enhance FunctionComponent to support display_name and improve d…
ogabrielluiz Nov 30, 2025
2bee586
feat: add tests for FunctionComponent pipelines with various configur…
ogabrielluiz Nov 30, 2025
31cb8f5
feat: add additional tests for FunctionComponent to improve coverage …
ogabrielluiz Nov 30, 2025
b7a471f
feat: add comprehensive tests for validate_code to enhance type detec…
ogabrielluiz Nov 30, 2025
2a5be66
feat: add tests for eval_custom_component_code to improve coverage an…
ogabrielluiz Nov 30, 2025
d4943db
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 30, 2025
328efaa
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Nov 30, 2025
7c1d85c
docs: enhance FunctionComponent and InputConfig documentation
ogabrielluiz Nov 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: enhance validate_code to support detection of function and clas…
…s components
  • Loading branch information
ogabrielluiz committed Nov 30, 2025
commit 1d6d65cb15bd35ce74af9769390b862f81b65f02
80 changes: 71 additions & 9 deletions src/lfx/src/lfx/custom/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,28 @@ class TypeIgnore(ast.AST):


def validate_code(code):
# Initialize the errors dictionary
errors = {"imports": {"errors": []}, "function": {"errors": []}}
"""Validate custom component code.

Supports both:
- Function-based components: Plain functions that will be wrapped as FunctionComponent
- Class-based components: Custom classes inheriting from Component

Returns:
dict with keys:
- imports: Import validation errors
- function: Syntax and execution validation errors
- detected_type: "function", "class", or "unknown"
- function_name: Name of function (if function code)
- class_name: Name of class (if class code)
"""
# Initialize the result dictionary
result = {
"imports": {"errors": []},
"function": {"errors": []},
"detected_type": "unknown",
"function_name": None,
"class_name": None,
}

# Parse the code string into an abstract syntax tree (AST)
try:
Expand All @@ -40,36 +60,78 @@ def validate_code(code):
logger.debug("Error parsing code", exc_info=True)
else:
logger.debug("Error parsing code")
errors["function"]["errors"].append(str(e))
return errors
result["function"]["errors"].append(str(e))
return result

# Add a dummy type_ignores field to the AST
add_type_ignores()
tree.type_ignores = []

# Detect code type and extract name
detected_type, name = _detect_code_type(tree)
result["detected_type"] = detected_type
if detected_type == "function":
result["function_name"] = name
elif detected_type == "class":
result["class_name"] = name

# Evaluate the import statements
for node in tree.body:
if isinstance(node, ast.Import):
for alias in node.names:
try:
importlib.import_module(alias.name)
except ModuleNotFoundError as e:
errors["imports"]["errors"].append(str(e))
result["imports"]["errors"].append(str(e))

# Evaluate the function definition with langflow context
for node in tree.body:
if isinstance(node, ast.FunctionDef):
if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef):
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef) uses a union type with isinstance(), but the second argument to isinstance() should be a tuple, not a union. This will raise a TypeError at runtime.

Change to: isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))

Suggested change
if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):

Copilot uses AI. Check for mistakes.
code_obj = compile(ast.Module(body=[node], type_ignores=[]), "<string>", "exec")
try:
# Create execution context with common langflow imports
exec_globals = _create_langflow_execution_context()
exec(code_obj, exec_globals)
except Exception as e: # noqa: BLE001
logger.debug("Error executing function code", exc_info=True)
errors["function"]["errors"].append(str(e))
result["function"]["errors"].append(str(e))

# Return the result dictionary
return result


def _detect_code_type(tree: ast.Module) -> tuple[str, str | None]:
"""Detect if code is function-based or class-based component.

# Return the errors dictionary
return errors
Args:
tree: AST module

Returns:
Tuple of (type, name) where type is "function", "class", or "unknown"
and name is the function or class name (or None if unknown)
"""
first_function_name = None
component_class_name = None

for node in tree.body:
# Check for function definitions
if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef):
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef) uses a union type with isinstance(), but the second argument to isinstance() should be a tuple, not a union. This will raise a TypeError at runtime.

Change to: isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))

Suggested change
if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):

Copilot uses AI. Check for mistakes.
if first_function_name is None:
first_function_name = node.name

# Check for Component class definitions
elif isinstance(node, ast.ClassDef):
for base in node.bases:
if isinstance(base, ast.Name) and any(pattern in base.id for pattern in ["Component", "LC"]):
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pattern check any(pattern in base.id for pattern in ["Component", "LC"]) is overly broad and could match unintended classes. For example, a class named MyLCDisplay or ComponentHelper that doesn't inherit from the Langflow Component class would incorrectly match.

Consider using more specific checks like base.id.endswith("Component") or maintain a list of known base class names.

Suggested change
if isinstance(base, ast.Name) and any(pattern in base.id for pattern in ["Component", "LC"]):
if isinstance(base, ast.Name) and (base.id.endswith("Component") or base.id == "LC"):

Copilot uses AI. Check for mistakes.
component_class_name = node.name
break

# Prefer class over function if both exist
if component_class_name:
return "class", component_class_name
if first_function_name:
return "function", first_function_name
return "unknown", None


def _create_langflow_execution_context():
Expand Down