-
Notifications
You must be signed in to change notification settings - Fork 8.2k
feat(lfx): Write components as plain Python functions with @component decorator #10798
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
1572154
4bf1a1a
3932cd4
c536920
38d2c4f
21e6d57
ad62df5
2a19cfe
a158e65
0a231f1
908dbb2
1d6d65c
2b85c47
ee503be
2bee586
31cb8f5
b7a471f
2a5be66
d4943db
328efaa
7c1d85c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
…s components
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -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: | ||||||
|
|
@@ -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): | ||||||
| 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): | ||||||
|
||||||
| if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef): | |
| if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
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.
| 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"): |
There was a problem hiding this comment.
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 withisinstance(), but the second argument toisinstance()should be a tuple, not a union. This will raise aTypeErrorat runtime.Change to:
isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))