feat: updated component with new Auth mode support.#9756
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughIntroduces schema-driven, dynamic authentication UI and JSON input handling, adds per-instance toolkit schema caching, refactors tool-loading to synchronous, removes a graph patch, and updates connection/auth lifecycle. Multiple helper methods are added; action population, validation, and error handling are adjusted within ComposioBaseComponent. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Component as ComposioBaseComponent
participant Toolkit as Toolkit Schema Cache
participant UI as Builder UI
User->>Component: Select toolkit / open config
Component->>Toolkit: _get_toolkit_schema()
Toolkit-->>Component: Schema (cached or fetched)
Component->>Component: _extract_auth_modes_from_schema(schema)
Component->>UI: _render_auth_mode_dropdown(modes)
alt User selects managed mode (e.g., OAUTH2)
Component->>UI: Render managed auth controls (auth link, status)
else User selects custom mode (e.g., API_KEY)
Component->>Component: _render_custom_auth_fields(schema, mode)
Component->>UI: Add dynamic fields (required/optional)
end
sequenceDiagram
autonumber
participant Component as ComposioBaseComponent
participant Actions as Actions Builder
participant Schema as Toolkit Schema
Component->>Actions: Populate actions
Component->>Schema: Inspect action input schema
alt Top-level object/array input
Component->>Actions: Render single Multiline/Code input
note right of Actions: Subfields skipped in flattening
else Primitive inputs
Component->>Actions: Render individual fields
end
sequenceDiagram
autonumber
participant Caller as Caller
participant Component as ComposioBaseComponent
participant Tools as Tools Registry
Caller->>Component: Request tools
Component->>Tools: _get_tools() (sync)
Tools-->>Component: List[Tool]
Component-->>Caller: Tools
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Pre-merge checks (1 passed, 2 warnings)❌ Failed Checks (2 warnings)
✅ Passed Checks (1 passed)
✨ Finishing touches🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Hi! I'm I would like to apply some automated changes to this pull request, but it looks like I don't have the necessary permissions to do so. To get this pull request into a mergeable state, please do one of the following two things:
|
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/lfx/src/lfx/base/composio/composio_base.py (1)
794-804: Don’t overwrite selected OAUTH2 auth_config; fallback only if none found.This unconditionally assigns the first item, discarding an OAUTH2 match found in the loop.
- else: - auth_config_id = None - for auth_config in auth_configs.items: - if auth_config.auth_scheme == "OAUTH2": - auth_config_id = auth_config.id - - auth_config_id = auth_configs.items[0].id + else: + auth_config_id = None + for auth_config in auth_configs.items: + if getattr(auth_config, "auth_scheme", None) == "OAUTH2": + auth_config_id = auth_config.id + break + if auth_config_id is None: + auth_config_id = auth_configs.items[0].id
🧹 Nitpick comments (7)
src/lfx/src/lfx/base/composio/composio_base.py (7)
354-376: Normalize file-upload field names to lowercase to match later.lower()checks.
inp.name.lower() in file_upload_fieldsassumes the set is lowercase. Normalize on insert to avoid mismatches.- if field_schema.get("file_uploadable") is True: - file_upload_fields.add(clean_field_name) + if field_schema.get("file_uploadable") is True: + file_upload_fields.add(clean_field_name.lower()) @@ - if isinstance(any_of_item, dict) and any_of_item.get("file_uploadable") is True: - file_upload_fields.add(clean_field_name) + if isinstance(any_of_item, dict) and any_of_item.get("file_uploadable") is True: + file_upload_fields.add(clean_field_name.lower()) @@ - file_upload_fields.add("attachment") # Attachment fields are also file upload fields + file_upload_fields.add("attachment") # already lowercase @@ - file_upload_fields = file_upload_fields | {"attachment"} + file_upload_fields = file_upload_fields | {"attachment"}Also applies to: 399-406, 606-609
960-1002: Handle empty/duplicate auth modes gracefully.If
modesis empty, hide the control; also dedupe/normalize to avoid inconsistent casing.- if len(modes) <= 1: + # Normalize/dedupe + modes = sorted({m.upper() for m in modes if isinstance(m, str)}) + if not modes: + auth_mode_cfg["show"] = False + return + if len(modes) == 1:
713-719: Avoid recomputing all action input schemas on every update.
_get_inputs_for_all_actionscalls_validate_schema_inputsfor every action, even when only one action is selected. Cache per-action inputs or compute on demand for the selected action to reduce update latency.Also applies to: 720-730
1958-1969: JSON coercion on execute: use schema of original field when renamed.For renamed fields (e.g.,
<app>_user_id),schema_properties.get(field)returns{}and JSON coercion is skipped. Consider mapping back to original names before schema lookup.Also applies to: 1974-1976
1221-1233: Soft-disconnect UX: consider clearing dynamic auth fields too.After soft disconnect, also clear/hide any custom auth fields rendered for a mode so the UI resets cleanly.
build_config["action_button"]["helper_text_metadata"] = {"variant": "destructive"} - return build_config + try: + schema = self._get_toolkit_schema() + self._clear_auth_fields_from_schema(build_config, schema) + except Exception: # noqa: BLE001 + pass + return build_config
943-959: Mode normalization: ensure API_KEY/BEARER/BASIC/OAUTH2 casing is consistent.Upcasing returned modes avoids UI mismatches when comparing with stored values and SDK outputs.
- return modes + return [m.upper() for m in modes]
596-609: Guard file-upload field comparison against case differences.If not normalizing set items, compare with both raw and lowered names to be robust.
- if inp.name.lower() in file_upload_fields or inp.name.lower() == "attachment": + if (inp.name in file_upload_fields) + or (inp.name.lower() in file_upload_fields) + or (inp.name.lower() == "attachment"):
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/lfx/src/lfx/base/composio/composio_base.py(25 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/lfx/src/lfx/base/composio/composio_base.py (1)
src/backend/base/langflow/api/v1/endpoints.py (1)
custom_component(666-684)
🪛 GitHub Check: Ruff Style Check (3.13)
src/lfx/src/lfx/base/composio/composio_base.py
[failure] 23-23: Ruff (F401)
src/lfx/src/lfx/base/composio/composio_base.py:23:5: F401 lfx.inputs.inputs.CodeInput imported but unused
[failure] 132-132: Ruff (BLE001)
src/lfx/src/lfx/base/composio/composio_base.py:132:16: BLE001 Do not catch blind exception: Exception
[failure] 616-616: Ruff (BLE001)
src/lfx/src/lfx/base/composio/composio_base.py:616:24: BLE001 Do not catch blind exception: Exception
[failure] 704-704: Ruff (BLE001)
src/lfx/src/lfx/base/composio/composio_base.py:704:24: BLE001 Do not catch blind exception: Exception
[failure] 874-874: Ruff (BLE001)
src/lfx/src/lfx/base/composio/composio_base.py:874:16: BLE001 Do not catch blind exception: Exception
[failure] 873-873: Ruff (TRY300)
src/lfx/src/lfx/base/composio/composio_base.py:873:13: TRY300 Consider moving this statement to an else block
[failure] 907-907: Ruff (TRY300)
src/lfx/src/lfx/base/composio/composio_base.py:907:13: TRY300 Consider moving this statement to an else block
[failure] 905-905: Ruff (BLE001)
src/lfx/src/lfx/base/composio/composio_base.py:905:24: BLE001 Do not catch blind exception: Exception
[failure] 900-900: Ruff (BLE001)
src/lfx/src/lfx/base/composio/composio_base.py:900:24: BLE001 Do not catch blind exception: Exception
🪛 GitHub Actions: Ruff Style Check
src/lfx/src/lfx/base/composio/composio_base.py
[error] 1-1: Ruff: I001 Import block is un-sorted or un-formatted (I001)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Update Starter Projects
🔇 Additional comments (3)
src/lfx/src/lfx/base/composio/composio_base.py (3)
1896-1901: Sync _get_tools matches synchronous to_toolkit usage.Switch to sync looks correct and aligns with the referenced sync caller. No further changes needed here.
1896-1901: No unguarded awaiters of_get_toolsremain; conversion to sync is safe.
10-24: Keep CodeInput import –CodeInputis instantiated later in this file to render JSON parent fields (e.g. within the loop at lines 686–688); do not remove it.Likely an incorrect or invalid review comment.
| from lfx.base.mcp.util import create_input_schema_from_json_schema | ||
| from lfx.custom.custom_component.component import Component | ||
| from lfx.inputs.inputs import AuthInput, FileInput, InputTypes, MessageTextInput, SecretStrInput, SortableListInput | ||
| from lfx.inputs.inputs import ( | ||
| AuthInput, | ||
| FileInput, | ||
| InputTypes, | ||
| MessageTextInput, | ||
| SecretStrInput, | ||
| SortableListInput, | ||
| DropdownInput, | ||
| StrInput, | ||
| TabInput, | ||
| MultilineInput, | ||
| CodeInput, | ||
| ) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Fix import ordering and remove unused CodeInput (ruff I001, F401).
Ruff is failing on unsorted imports and an unused symbol. Sort imports into stdlib/third-party/local groups and drop CodeInput.
-import copy
-import re
-from typing import Any
-import json
+import copy
+import json
+import re
+from typing import Any
@@
-from lfx.inputs.inputs import (
- AuthInput,
- FileInput,
- InputTypes,
- MessageTextInput,
- SecretStrInput,
- SortableListInput,
- DropdownInput,
- StrInput,
- TabInput,
- MultilineInput,
- CodeInput,
-)
+from lfx.inputs.inputs import (
+ AuthInput,
+ DropdownInput,
+ FileInput,
+ InputTypes,
+ MessageTextInput,
+ MultilineInput,
+ SecretStrInput,
+ SortableListInput,
+ StrInput,
+ TabInput,
+)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| from lfx.base.mcp.util import create_input_schema_from_json_schema | |
| from lfx.custom.custom_component.component import Component | |
| from lfx.inputs.inputs import AuthInput, FileInput, InputTypes, MessageTextInput, SecretStrInput, SortableListInput | |
| from lfx.inputs.inputs import ( | |
| AuthInput, | |
| FileInput, | |
| InputTypes, | |
| MessageTextInput, | |
| SecretStrInput, | |
| SortableListInput, | |
| DropdownInput, | |
| StrInput, | |
| TabInput, | |
| MultilineInput, | |
| CodeInput, | |
| ) | |
| import copy | |
| import json | |
| import re | |
| from typing import Any | |
| from lfx.base.mcp.util import create_input_schema_from_json_schema | |
| from lfx.custom.custom_component.component import Component | |
| from lfx.inputs.inputs import ( | |
| AuthInput, | |
| DropdownInput, | |
| FileInput, | |
| InputTypes, | |
| MessageTextInput, | |
| MultilineInput, | |
| SecretStrInput, | |
| SortableListInput, | |
| StrInput, | |
| TabInput, | |
| ) |
🧰 Tools
🪛 GitHub Check: Ruff Style Check (3.13)
[failure] 23-23: Ruff (F401)
src/lfx/src/lfx/base/composio/composio_base.py:23:5: F401 lfx.inputs.inputs.CodeInput imported but unused
🤖 Prompt for AI Agents
In src/lfx/src/lfx/base/composio/composio_base.py around lines 10 to 24, the
import block is unsorted and includes an unused symbol CodeInput causing ruff
errors (I001, F401); reorder imports into stdlib, third-party, then local groups
and remove CodeInput from the inputs import list, keeping only the actually used
names (AuthInput, FileInput, InputTypes, MessageTextInput, SecretStrInput,
SortableListInput, DropdownInput, StrInput, TabInput, MultilineInput), ensuring
alphabetic ordering within each group.
| # which interferes with logging utilities that probe for '.data'. | ||
| df = DataFrame(result) | ||
| try: | ||
| if "data" in df.columns: | ||
| df = df.rename(columns={"data": "_data"}) | ||
| except Exception: | ||
| # If any unexpected structure, return the DataFrame as-is | ||
| pass | ||
| return df |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Narrow broad except in as_dataframe (ruff BLE001).
Catching all Exceptions here hides real errors and fails Ruff. Limit to likely issues.
- try:
+ try:
if "data" in df.columns:
df = df.rename(columns={"data": "_data"})
- except Exception:
+ except (AttributeError, KeyError, TypeError):
# If any unexpected structure, return the DataFrame as-is
pass📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # which interferes with logging utilities that probe for '.data'. | |
| df = DataFrame(result) | |
| try: | |
| if "data" in df.columns: | |
| df = df.rename(columns={"data": "_data"}) | |
| except Exception: | |
| # If any unexpected structure, return the DataFrame as-is | |
| pass | |
| return df | |
| # which interferes with logging utilities that probe for '.data'. | |
| df = DataFrame(result) | |
| try: | |
| if "data" in df.columns: | |
| df = df.rename(columns={"data": "_data"}) | |
| except (AttributeError, KeyError, TypeError): | |
| # If any unexpected structure, return the DataFrame as-is | |
| pass | |
| return df |
🧰 Tools
🪛 GitHub Check: Ruff Style Check (3.13)
[failure] 132-132: Ruff (BLE001)
src/lfx/src/lfx/base/composio/composio_base.py:132:16: BLE001 Do not catch blind exception: Exception
🤖 Prompt for AI Agents
In src/lfx/src/lfx/base/composio/composio_base.py around lines 127 to 135, the
broad "except Exception" in as_dataframe should be narrowed to only the expected
errors so real bugs aren't swallowed and Ruff BLE001 is satisfied; replace the
bare except with a specific exception tuple (e.g. except (KeyError,
AttributeError, TypeError, ValueError):) that covers likely issues when
checking/renaming columns, or handle pandas-specific errors if applicable, and
leave the existing fallback behavior (pass and return df) intact.
| field_schema_copy = field_schema.copy() | ||
| field_schema_copy["description"] = ( | ||
| f"User ID for {self.app_name.title()}: " + field_schema["description"] | ||
| ) |
There was a problem hiding this comment.
Avoid KeyError when augmenting user_id description.
field_schema["description"] may be missing. Use .get to prevent a crash.
- field_schema_copy["description"] = (
- f"User ID for {self.app_name.title()}: " + field_schema["description"]
- )
+ field_schema_copy["description"] = (
+ f"User ID for {self.app_name.title()}: "
+ + field_schema.get("description", "")
+ )📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| field_schema_copy = field_schema.copy() | |
| field_schema_copy["description"] = ( | |
| f"User ID for {self.app_name.title()}: " + field_schema["description"] | |
| ) | |
| field_schema_copy = field_schema.copy() | |
| field_schema_copy["description"] = ( | |
| f"User ID for {self.app_name.title()}: " | |
| field_schema.get("description", "") | |
| ) |
🤖 Prompt for AI Agents
In src/lfx/src/lfx/base/composio/composio_base.py around lines 548 to 551, the
code assumes field_schema["description"] always exists which can raise a
KeyError; change it to use field_schema.get("description", "") (or a suitable
default string) when constructing the augmented description so missing
descriptions don't crash, e.g. read the original with .get and then prepend the
"User ID for {self.app_name.title()}: " text before assigning to
field_schema_copy["description"].
| # Identify top-level JSON parents (object/array) to render as single CodeInput | ||
| top_props_for_json = set() | ||
| try: | ||
| for top_name, top_schema in parameters_schema.get("properties", {}).items(): | ||
| if isinstance(top_schema, dict) and top_schema.get("type") in {"object", "array"}: | ||
| top_props_for_json.add(top_name) | ||
| except Exception: | ||
| pass |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Narrow broad excepts in schema processing (ruff BLE001).
Limit to type/shape errors in these defensive blocks.
- except Exception:
+ except (AttributeError, TypeError):
pass
@@
- except Exception:
+ except (KeyError, TypeError):
passAlso applies to: 704-705
🧰 Tools
🪛 GitHub Check: Ruff Style Check (3.13)
[failure] 616-616: Ruff (BLE001)
src/lfx/src/lfx/base/composio/composio_base.py:616:24: BLE001 Do not catch blind exception: Exception
🤖 Prompt for AI Agents
In src/lfx/src/lfx/base/composio/composio_base.py around lines 610-617, the
broad "except Exception:" swallowing all errors should be narrowed to only
expected type/shape issues; replace it with a targeted catch such as "except
(AttributeError, TypeError):" (or include KeyError/ValueError if you confirm
those can occur) so only schema shape/access problems are handled, and make the
same change for the similar defensive block at lines 704-705.
| def _get_connection_auth_info(self, connection_id: str) -> tuple[str | None, bool | None]: | ||
| """Return (auth_scheme, is_composio_managed) for a given connection id, if available.""" | ||
| try: | ||
| composio = self._build_wrapper() | ||
| connection = composio.connected_accounts.get(nanoid=connection_id) | ||
| auth_config = getattr(connection, "auth_config", None) | ||
| if auth_config is None and hasattr(connection, "__dict__"): | ||
| auth_config = getattr(connection.__dict__, "auth_config", None) | ||
| scheme = getattr(auth_config, "auth_scheme", None) if auth_config else None | ||
| is_managed = getattr(auth_config, "is_composio_managed", None) if auth_config else None | ||
| return scheme, is_managed | ||
| except Exception as e: | ||
| logger.debug(f"Could not retrieve auth info for connection {connection_id}: {e}") | ||
| return None, None | ||
|
|
There was a problem hiding this comment.
Fix dict attribute access and address Ruff TRY300/BLE001 in _get_connection_auth_info.
getattr(connection.__dict__, "auth_config", None) is wrong; __dict__ is a dict. Also restructure try/except to use else and narrow exceptions.
def _get_connection_auth_info(self, connection_id: str) -> tuple[str | None, bool | None]:
"""Return (auth_scheme, is_composio_managed) for a given connection id, if available."""
- try:
+ try:
composio = self._build_wrapper()
connection = composio.connected_accounts.get(nanoid=connection_id)
auth_config = getattr(connection, "auth_config", None)
- if auth_config is None and hasattr(connection, "__dict__"):
- auth_config = getattr(connection.__dict__, "auth_config", None)
+ if auth_config is None:
+ data = getattr(connection, "__dict__", None)
+ if isinstance(data, dict):
+ auth_config = data.get("auth_config")
scheme = getattr(auth_config, "auth_scheme", None) if auth_config else None
is_managed = getattr(auth_config, "is_composio_managed", None) if auth_config else None
- return scheme, is_managed
- except Exception as e:
+ except (ValueError, ConnectionError, AttributeError, KeyError) as e:
logger.debug(f"Could not retrieve auth info for connection {connection_id}: {e}")
- return None, None
+ return None, None
+ else:
+ return scheme, is_managed📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def _get_connection_auth_info(self, connection_id: str) -> tuple[str | None, bool | None]: | |
| """Return (auth_scheme, is_composio_managed) for a given connection id, if available.""" | |
| try: | |
| composio = self._build_wrapper() | |
| connection = composio.connected_accounts.get(nanoid=connection_id) | |
| auth_config = getattr(connection, "auth_config", None) | |
| if auth_config is None and hasattr(connection, "__dict__"): | |
| auth_config = getattr(connection.__dict__, "auth_config", None) | |
| scheme = getattr(auth_config, "auth_scheme", None) if auth_config else None | |
| is_managed = getattr(auth_config, "is_composio_managed", None) if auth_config else None | |
| return scheme, is_managed | |
| except Exception as e: | |
| logger.debug(f"Could not retrieve auth info for connection {connection_id}: {e}") | |
| return None, None | |
| def _get_connection_auth_info(self, connection_id: str) -> tuple[str | None, bool | None]: | |
| """Return (auth_scheme, is_composio_managed) for a given connection id, if available.""" | |
| try: | |
| composio = self._build_wrapper() | |
| connection = composio.connected_accounts.get(nanoid=connection_id) | |
| auth_config = getattr(connection, "auth_config", None) | |
| if auth_config is None: | |
| data = getattr(connection, "__dict__", None) | |
| if isinstance(data, dict): | |
| auth_config = data.get("auth_config") | |
| scheme = getattr(auth_config, "auth_scheme", None) if auth_config else None | |
| is_managed = getattr(auth_config, "is_composio_managed", None) if auth_config else None | |
| except (ValueError, ConnectionError, AttributeError, KeyError) as e: | |
| logger.debug(f"Could not retrieve auth info for connection {connection_id}: {e}") | |
| return None, None | |
| else: | |
| return scheme, is_managed |
🧰 Tools
🪛 GitHub Check: Ruff Style Check (3.13)
[failure] 874-874: Ruff (BLE001)
src/lfx/src/lfx/base/composio/composio_base.py:874:16: BLE001 Do not catch blind exception: Exception
[failure] 873-873: Ruff (TRY300)
src/lfx/src/lfx/base/composio/composio_base.py:873:13: TRY300 Consider moving this statement to an else block
| def _to_plain_dict(self, obj: Any) -> Any: | ||
| """Recursively convert SDK models/lists to plain Python dicts/lists for safe .get access.""" | ||
| try: | ||
| if isinstance(obj, dict): | ||
| return {k: self._to_plain_dict(v) for k, v in obj.items()} | ||
| if isinstance(obj, (list, tuple, set)): | ||
| return [self._to_plain_dict(v) for v in obj] | ||
| if hasattr(obj, "model_dump"): | ||
| try: | ||
| return self._to_plain_dict(obj.model_dump()) | ||
| except Exception: | ||
| pass | ||
| if hasattr(obj, "__dict__") and not isinstance(obj, (str, bytes)): | ||
| try: | ||
| return self._to_plain_dict({k: v for k, v in obj.__dict__.items() if not k.startswith("_")}) | ||
| except Exception: | ||
| pass | ||
| return obj | ||
| except Exception: | ||
| return obj | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Refactor _to_plain_dict to avoid broad try and TRY300.
Remove the outer broad try/except; keep targeted inner guards.
- def _to_plain_dict(self, obj: Any) -> Any:
- """Recursively convert SDK models/lists to plain Python dicts/lists for safe .get access."""
- try:
- if isinstance(obj, dict):
- return {k: self._to_plain_dict(v) for k, v in obj.items()}
- if isinstance(obj, (list, tuple, set)):
- return [self._to_plain_dict(v) for v in obj]
- if hasattr(obj, "model_dump"):
- try:
- return self._to_plain_dict(obj.model_dump())
- except Exception:
- pass
- if hasattr(obj, "__dict__") and not isinstance(obj, (str, bytes)):
- try:
- return self._to_plain_dict({k: v for k, v in obj.__dict__.items() if not k.startswith("_")})
- except Exception:
- pass
- return obj
- except Exception:
- return obj
+ def _to_plain_dict(self, obj: Any) -> Any:
+ """Recursively convert SDK models/lists to plain Python dicts/lists for safe .get access."""
+ if isinstance(obj, dict):
+ return {k: self._to_plain_dict(v) for k, v in obj.items()}
+ if isinstance(obj, (list, tuple, set)):
+ return [self._to_plain_dict(v) for v in obj]
+ if hasattr(obj, "model_dump"):
+ try:
+ return self._to_plain_dict(obj.model_dump())
+ except Exception: # noqa: BLE001
+ pass
+ if hasattr(obj, "__dict__") and not isinstance(obj, (str, bytes)):
+ try:
+ return self._to_plain_dict({k: v for k, v in obj.__dict__.items() if not k.startswith("_")})
+ except Exception: # noqa: BLE001
+ pass
+ return obj📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def _to_plain_dict(self, obj: Any) -> Any: | |
| """Recursively convert SDK models/lists to plain Python dicts/lists for safe .get access.""" | |
| try: | |
| if isinstance(obj, dict): | |
| return {k: self._to_plain_dict(v) for k, v in obj.items()} | |
| if isinstance(obj, (list, tuple, set)): | |
| return [self._to_plain_dict(v) for v in obj] | |
| if hasattr(obj, "model_dump"): | |
| try: | |
| return self._to_plain_dict(obj.model_dump()) | |
| except Exception: | |
| pass | |
| if hasattr(obj, "__dict__") and not isinstance(obj, (str, bytes)): | |
| try: | |
| return self._to_plain_dict({k: v for k, v in obj.__dict__.items() if not k.startswith("_")}) | |
| except Exception: | |
| pass | |
| return obj | |
| except Exception: | |
| return obj | |
| def _to_plain_dict(self, obj: Any) -> Any: | |
| """Recursively convert SDK models/lists to plain Python dicts/lists for safe .get access.""" | |
| if isinstance(obj, dict): | |
| return {k: self._to_plain_dict(v) for k, v in obj.items()} | |
| if isinstance(obj, (list, tuple, set)): | |
| return [self._to_plain_dict(v) for v in obj] | |
| if hasattr(obj, "model_dump"): | |
| try: | |
| return self._to_plain_dict(obj.model_dump()) | |
| except Exception: # noqa: BLE001 | |
| pass | |
| if hasattr(obj, "__dict__") and not isinstance(obj, (str, bytes)): | |
| try: | |
| return self._to_plain_dict({k: v for k, v in obj.__dict__.items() if not k.startswith("_")}) | |
| except Exception: # noqa: BLE001 | |
| pass | |
| return obj |
🧰 Tools
🪛 GitHub Check: Ruff Style Check (3.13)
[failure] 907-907: Ruff (TRY300)
src/lfx/src/lfx/base/composio/composio_base.py:907:13: TRY300 Consider moving this statement to an else block
[failure] 905-905: Ruff (BLE001)
src/lfx/src/lfx/base/composio/composio_base.py:905:24: BLE001 Do not catch blind exception: Exception
[failure] 900-900: Ruff (BLE001)
src/lfx/src/lfx/base/composio/composio_base.py:900:24: BLE001 Do not catch blind exception: Exception
🤖 Prompt for AI Agents
In src/lfx/src/lfx/base/composio/composio_base.py around lines 890 to 910,
remove the outermost try/except that swallows all exceptions and instead rely on
the existing targeted inner guards; keep the isinstance checks for
dict/list/tuple/set, the hasattr(obj, "model_dump") block with its inner
try/except, and the hasattr(obj, "__dict__") block with its inner try/except,
and ensure the final return falls through to returning obj (no broad exception
handling). Adjust indentation accordingly and delete the outer except block so
unexpected errors surface while preserving the inner defensive catches.
| build_config.setdefault("auth_link", {}) | ||
| build_config["auth_link"]["show"] = False | ||
| # Reset connection state when switching modes | ||
| build_config["auth_link"].pop("connection_id", None) | ||
| build_config["auth_link"].pop("auth_config_id", None) | ||
| build_config["auth_link"]["value"] = "connect" | ||
| build_config["auth_link"]["auth_tooltip"] = "Connect" |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Auth link should be visible on mode change (comment contradicts code).
Comment says “Always show auth_link,” but show=False hides it. Set to True.
- build_config.setdefault("auth_link", {})
- build_config["auth_link"]["show"] = False
+ build_config.setdefault("auth_link", {})
+ build_config["auth_link"]["show"] = True📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| build_config.setdefault("auth_link", {}) | |
| build_config["auth_link"]["show"] = False | |
| # Reset connection state when switching modes | |
| build_config["auth_link"].pop("connection_id", None) | |
| build_config["auth_link"].pop("auth_config_id", None) | |
| build_config["auth_link"]["value"] = "connect" | |
| build_config["auth_link"]["auth_tooltip"] = "Connect" | |
| build_config.setdefault("auth_link", {}) | |
| build_config["auth_link"]["show"] = True | |
| # Reset connection state when switching modes | |
| build_config["auth_link"].pop("connection_id", None) | |
| build_config["auth_link"].pop("auth_config_id", None) | |
| build_config["auth_link"]["value"] = "connect" | |
| build_config["auth_link"]["auth_tooltip"] = "Connect" |
🤖 Prompt for AI Agents
In src/lfx/src/lfx/base/composio/composio_base.py around lines 1243 to 1249, the
code sets build_config["auth_link"]["show"] = False while the comment states
"Always show auth_link"; change the value to True so the auth link is visible on
mode change and update the inline comment to match (or remove the contradictory
comment) and keep the rest of the auth_link population logic unchanged.
| build_config.setdefault("auth_link", {}) | ||
| build_config["auth_link"]["show"] = False | ||
| build_config["auth_link"]["display_name"] = "" | ||
| try: | ||
| schema = self._get_toolkit_schema() | ||
| mode = (build_config.get("auth_mode") or {}).get("value") | ||
| managed = (schema or {}).get("composio_managed_auth_schemes") or [] | ||
| if mode and not (isinstance(managed, list) and mode in managed): | ||
| if not getattr(self, "_auth_dynamic_fields", set()): | ||
| self._render_custom_auth_fields(build_config, schema or {}, mode) | ||
| except Exception: |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Keep auth UI available in Tool Mode.
Block intends to keep auth UI, but hides auth_link. Make it visible.
- build_config.setdefault("auth_link", {})
- build_config["auth_link"]["show"] = False
- build_config["auth_link"]["display_name"] = ""
+ build_config.setdefault("auth_link", {})
+ build_config["auth_link"]["show"] = True
+ # Keep default display_name📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| build_config.setdefault("auth_link", {}) | |
| build_config["auth_link"]["show"] = False | |
| build_config["auth_link"]["display_name"] = "" | |
| try: | |
| schema = self._get_toolkit_schema() | |
| mode = (build_config.get("auth_mode") or {}).get("value") | |
| managed = (schema or {}).get("composio_managed_auth_schemes") or [] | |
| if mode and not (isinstance(managed, list) and mode in managed): | |
| if not getattr(self, "_auth_dynamic_fields", set()): | |
| self._render_custom_auth_fields(build_config, schema or {}, mode) | |
| except Exception: | |
| build_config.setdefault("auth_link", {}) | |
| build_config["auth_link"]["show"] = True | |
| # Keep default display_name | |
| try: | |
| schema = self._get_toolkit_schema() | |
| mode = (build_config.get("auth_mode") or {}).get("value") | |
| managed = (schema or {}).get("composio_managed_auth_schemes") or [] | |
| if mode and not (isinstance(managed, list) and mode in managed): | |
| if not getattr(self, "_auth_dynamic_fields", set()): | |
| self._render_custom_auth_fields(build_config, schema or {}, mode) | |
| except Exception: |
🤖 Prompt for AI Agents
In src/lfx/src/lfx/base/composio/composio_base.py around lines 1654 to 1664, the
code unconditionally sets build_config["auth_link"]["show"] = False which hides
the auth UI in Tool Mode; update this to keep the auth UI visible by setting
build_config["auth_link"]["show"] = True (or remove the line so existing
visibility is preserved), and ensure display_name remains set as intended; no
other logic changes required.
edwinjosechittilappilly
left a comment
There was a problem hiding this comment.
LGTM.
Please add test for each of these functions in base.
|
* feat: updated component with new Auth mode support. * fix: format --------- Co-authored-by: Edwin Jose <edwin.jose@datastax.com>



Update: Composio Base Component
File:
/src/backend/base/langflow/base/composio/composio_base.pyAPI-KEY,BEARER,BASIC) and allow custom OAuth2 credentials for components without a default Composio Auth App.Error:

Fix:
Changed
_get_toolsfrom async to sync, sinceto_toolkitis synchronous incomponent.py.Summary by CodeRabbit
New Features
Improvements
Bug Fixes