lfx-nightly 0.1.12.dev34__py3-none-any.whl → 0.1.12.dev36__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of lfx-nightly might be problematic. Click here for more details.

@@ -1,6 +1,7 @@
1
1
  import copy
2
2
  import json
3
3
  import re
4
+ from contextlib import suppress
4
5
  from typing import Any
5
6
 
6
7
  from composio import Composio
@@ -381,9 +382,9 @@ class ComposioBaseComponent(Component):
381
382
  if clean_field == "user_id":
382
383
  clean_field = f"{self.app_name}_user_id"
383
384
 
384
- # Handle reserved attribute name conflicts (e.g., 'status') by prefixing with app name
385
- # This prevents clashes with component attributes like self.status
386
- if clean_field in {"status"}:
385
+ # Handle reserved attribute name conflicts (e.g., 'status', 'name')
386
+ # Prefix with app name to prevent clashes with component attributes
387
+ if clean_field in {"status", "name"}:
387
388
  clean_field = f"{self.app_name}_{clean_field}"
388
389
 
389
390
  action_fields.append(clean_field)
@@ -549,6 +550,13 @@ class ComposioBaseComponent(Component):
549
550
  field_schema_copy["description"] = f"Status for {self.app_name.title()}: " + field_schema.get(
550
551
  "description", ""
551
552
  )
553
+ elif clean_field_name == "name":
554
+ clean_field_name = f"{self.app_name}_name"
555
+ # Update the field schema description to reflect the name change
556
+ field_schema_copy = field_schema.copy()
557
+ field_schema_copy["description"] = f"Name for {self.app_name.title()}: " + field_schema.get(
558
+ "description", ""
559
+ )
552
560
  else:
553
561
  # Use the original field schema for all other fields
554
562
  field_schema_copy = field_schema
@@ -571,7 +579,17 @@ class ComposioBaseComponent(Component):
571
579
 
572
580
  # Also update required fields to match cleaned names
573
581
  if flat_schema.get("required"):
574
- cleaned_required = [field.replace("[0]", "") for field in flat_schema["required"]]
582
+ cleaned_required = []
583
+ for field in flat_schema["required"]:
584
+ base = field.replace("[0]", "")
585
+ if base == "user_id":
586
+ cleaned_required.append(f"{self.app_name}_user_id")
587
+ elif base == "status":
588
+ cleaned_required.append(f"{self.app_name}_status")
589
+ elif base == "name":
590
+ cleaned_required.append(f"{self.app_name}_name")
591
+ else:
592
+ cleaned_required.append(base)
575
593
  flat_schema["required"] = cleaned_required
576
594
 
577
595
  input_schema = create_input_schema_from_json_schema(flat_schema)
@@ -756,6 +774,9 @@ class ComposioBaseComponent(Component):
756
774
  # Ensure _all_fields includes new ones
757
775
  self._all_fields.update({i.name for i in lf_inputs if i.name is not None})
758
776
 
777
+ # Normalize input_types to prevent None values
778
+ self.update_input_types(build_config)
779
+
759
780
  def _is_tool_mode_enabled(self) -> bool:
760
781
  """Check if tool_mode is currently enabled."""
761
782
  return getattr(self, "tool_mode", False)
@@ -1094,6 +1115,20 @@ class ComposioBaseComponent(Component):
1094
1115
  # Also clear any tracked dynamic fields
1095
1116
  self._clear_auth_dynamic_fields(build_config)
1096
1117
 
1118
+ def update_input_types(self, build_config: dict) -> dict:
1119
+ """Normalize input_types to [] wherever None appears in the build_config template."""
1120
+ try:
1121
+ for key, value in list(build_config.items()):
1122
+ if isinstance(value, dict):
1123
+ if value.get("input_types") is None:
1124
+ build_config[key]["input_types"] = []
1125
+ elif hasattr(value, "input_types") and value.input_types is None:
1126
+ with suppress(AttributeError, TypeError):
1127
+ value.input_types = []
1128
+ except (RuntimeError, KeyError):
1129
+ pass
1130
+ return build_config
1131
+
1097
1132
  def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None) -> dict:
1098
1133
  """Update build config for auth and action selection."""
1099
1134
  # Avoid normalizing legacy input_types here; rely on upstream fixes
@@ -1233,7 +1268,7 @@ class ComposioBaseComponent(Component):
1233
1268
  build_config["auth_link"].pop("connection_id", None)
1234
1269
  build_config["action_button"]["helper_text"] = "Please connect before selecting actions."
1235
1270
  build_config["action_button"]["helper_text_metadata"] = {"variant": "destructive"}
1236
- return build_config
1271
+ return self.update_input_types(build_config)
1237
1272
 
1238
1273
  # Handle auth mode change -> render appropriate fields based on schema
1239
1274
  if field_name == "auth_mode":
@@ -1290,7 +1325,7 @@ class ComposioBaseComponent(Component):
1290
1325
  }
1291
1326
  build_config["action_button"]["helper_text"] = ""
1292
1327
  build_config["action_button"]["helper_text_metadata"] = {}
1293
- return build_config
1328
+ return self.update_input_types(build_config)
1294
1329
  if mode:
1295
1330
  managed = schema.get("composio_managed_auth_schemes") or []
1296
1331
  # Always hide the Create Auth Config control (used internally only)
@@ -1306,7 +1341,7 @@ class ComposioBaseComponent(Component):
1306
1341
  else:
1307
1342
  # Custom → render only required fields based on the toolkit schema
1308
1343
  self._render_custom_auth_fields(build_config, schema, mode)
1309
- return build_config
1344
+ return self.update_input_types(build_config)
1310
1345
 
1311
1346
  # Handle connection initiation when tool mode is enabled
1312
1347
  if field_name == "auth_link" and isinstance(field_value, dict):
@@ -1323,7 +1358,7 @@ class ComposioBaseComponent(Component):
1323
1358
  build_config["action_button"]["helper_text"] = ""
1324
1359
  build_config["action_button"]["helper_text_metadata"] = {}
1325
1360
  logger.info(f"Using existing ACTIVE connection {connection_id} for {toolkit_slug}")
1326
- return build_config
1361
+ return self.update_input_types(build_config)
1327
1362
 
1328
1363
  # Only reuse ACTIVE connections; otherwise create a new connection
1329
1364
  stored_connection_id = None
@@ -1342,11 +1377,11 @@ class ComposioBaseComponent(Component):
1342
1377
  redirect_url, connection_id = self._initiate_connection(toolkit_slug)
1343
1378
  build_config["auth_link"]["value"] = redirect_url
1344
1379
  logger.info(f"New OAuth URL created for {toolkit_slug}: {redirect_url}")
1345
- return build_config
1380
+ return self.update_input_types(build_config)
1346
1381
  if not mode:
1347
1382
  build_config["auth_link"]["value"] = "connect"
1348
1383
  build_config["auth_link"]["auth_tooltip"] = "Select Auth Mode"
1349
- return build_config
1384
+ return self.update_input_types(build_config)
1350
1385
  # Custom modes: create auth config and/or initiate with config
1351
1386
  # Validate required fields before creating any auth config
1352
1387
  required_missing = []
@@ -1403,7 +1438,7 @@ class ComposioBaseComponent(Component):
1403
1438
  build_config["auth_mode"]["helper_text_metadata"] = {"variant": "destructive"}
1404
1439
  build_config["auth_link"]["value"] = "connect"
1405
1440
  build_config["auth_link"]["auth_tooltip"] = f"Missing: {missing_joined}"
1406
- return build_config
1441
+ return self.update_input_types(build_config)
1407
1442
  composio = self._build_wrapper()
1408
1443
  if mode == "OAUTH2":
1409
1444
  # If an auth_config was already created via the button, use it and include initiation fields
@@ -1437,7 +1472,7 @@ class ComposioBaseComponent(Component):
1437
1472
  # Clear action blocker text on successful initiation
1438
1473
  build_config["action_button"]["helper_text"] = ""
1439
1474
  build_config["action_button"]["helper_text_metadata"] = {}
1440
- return build_config
1475
+ return self.update_input_types(build_config)
1441
1476
  # Otherwise, create custom OAuth2 auth config using schema-declared required fields
1442
1477
  credentials = {}
1443
1478
  missing = []
@@ -1487,7 +1522,7 @@ class ComposioBaseComponent(Component):
1487
1522
  build_config["auth_link"]["auth_config_id"] = auth_config_id
1488
1523
  build_config["auth_link"]["value"] = "connect"
1489
1524
  build_config["auth_link"]["auth_tooltip"] = "Connect"
1490
- return build_config
1525
+ return self.update_input_types(build_config)
1491
1526
  # Otherwise initiate immediately
1492
1527
  redirect = composio.connected_accounts.initiate(
1493
1528
  user_id=self.entity_id,
@@ -1504,7 +1539,7 @@ class ComposioBaseComponent(Component):
1504
1539
  self._clear_auth_fields_from_schema(build_config, schema)
1505
1540
  build_config["action_button"]["helper_text"] = ""
1506
1541
  build_config["action_button"]["helper_text_metadata"] = {}
1507
- return build_config
1542
+ return self.update_input_types(build_config)
1508
1543
  if mode == "API_KEY":
1509
1544
  ac = composio.auth_configs.create(
1510
1545
  toolkit=toolkit_slug,
@@ -1550,7 +1585,7 @@ class ComposioBaseComponent(Component):
1550
1585
  self._clear_auth_fields_from_schema(build_config, schema)
1551
1586
  build_config["action_button"]["helper_text"] = ""
1552
1587
  build_config["action_button"]["helper_text_metadata"] = {}
1553
- return build_config
1588
+ return self.update_input_types(build_config)
1554
1589
  # Generic custom auth flow for any other mode (treat like API_KEY)
1555
1590
  ac = composio.auth_configs.create(
1556
1591
  toolkit=toolkit_slug,
@@ -1584,13 +1619,13 @@ class ComposioBaseComponent(Component):
1584
1619
  else:
1585
1620
  build_config["auth_link"]["value"] = "validated"
1586
1621
  build_config["auth_link"]["auth_tooltip"] = "Disconnect"
1587
- return build_config
1622
+ return self.update_input_types(build_config)
1588
1623
  except (ValueError, ConnectionError, TypeError) as e:
1589
1624
  logger.error(f"Error creating connection: {e}")
1590
1625
  build_config["auth_link"]["value"] = "connect"
1591
1626
  build_config["auth_link"]["auth_tooltip"] = f"Error: {e!s}"
1592
1627
  else:
1593
- return build_config
1628
+ return self.update_input_types(build_config)
1594
1629
  else:
1595
1630
  # We already have a usable connection; no new OAuth request
1596
1631
  build_config["auth_link"]["auth_tooltip"] = "Disconnect"
@@ -1732,7 +1767,7 @@ class ComposioBaseComponent(Component):
1732
1767
  build_config["action_button"]["show"] = True # Show action field when tool mode is disabled
1733
1768
  for field in self._all_fields:
1734
1769
  build_config[field]["show"] = True # Update show status for all fields based on tool mode
1735
- return build_config
1770
+ return self.update_input_types(build_config)
1736
1771
 
1737
1772
  if field_name == "action_button":
1738
1773
  # If selection is cancelled/cleared, remove generated fields
@@ -1748,12 +1783,12 @@ class ComposioBaseComponent(Component):
1748
1783
 
1749
1784
  if _is_cleared(field_value):
1750
1785
  self._hide_all_action_fields(build_config)
1751
- return build_config
1786
+ return self.update_input_types(build_config)
1752
1787
 
1753
1788
  self._update_action_config(build_config, field_value)
1754
1789
  # Keep the existing show/hide behaviour
1755
1790
  self.show_hide_fields(build_config, field_value)
1756
- return build_config
1791
+ return self.update_input_types(build_config)
1757
1792
 
1758
1793
  # Handle auth config button click
1759
1794
  if field_name == "create_auth_config" and field_value == "create":
@@ -1798,7 +1833,7 @@ class ComposioBaseComponent(Component):
1798
1833
  build_config["auth_link"]["auth_config_id"] = auth_config_id
1799
1834
  build_config["auth_link"]["value"] = "connect"
1800
1835
  build_config["auth_link"]["auth_tooltip"] = "Connect"
1801
- return build_config
1836
+ return self.update_input_types(build_config)
1802
1837
  # If no initiation fields required, initiate immediately
1803
1838
  connection_request = composio.connected_accounts.initiate(
1804
1839
  user_id=self.entity_id, auth_config_id=auth_config_id
@@ -1824,7 +1859,7 @@ class ComposioBaseComponent(Component):
1824
1859
  logger.error(f"Error creating new auth config: {e}")
1825
1860
  build_config["auth_link"]["value"] = "error"
1826
1861
  build_config["auth_link"]["auth_tooltip"] = f"Error: {e!s}"
1827
- return build_config
1862
+ return self.update_input_types(build_config)
1828
1863
 
1829
1864
  # Handle API key removal
1830
1865
  if field_name == "api_key" and len(field_value) == 0:
@@ -1861,16 +1896,16 @@ class ComposioBaseComponent(Component):
1861
1896
  self._hide_all_action_fields(build_config)
1862
1897
  except (TypeError, ValueError, AttributeError):
1863
1898
  pass
1864
- return build_config
1899
+ return self.update_input_types(build_config)
1865
1900
 
1866
1901
  # Only proceed with connection logic if we have an API key
1867
1902
  if not hasattr(self, "api_key") or not self.api_key:
1868
- return build_config
1903
+ return self.update_input_types(build_config)
1869
1904
 
1870
1905
  # CRITICAL: If tool_mode is enabled (check both instance and build_config), skip all connection logic
1871
1906
  if current_tool_mode:
1872
1907
  build_config["action_button"]["show"] = False
1873
- return build_config
1908
+ return self.update_input_types(build_config)
1874
1909
 
1875
1910
  # Update action options only if tool_mode is disabled
1876
1911
  self._build_action_maps()
@@ -1931,7 +1966,7 @@ class ComposioBaseComponent(Component):
1931
1966
  if self._is_tool_mode_enabled():
1932
1967
  build_config["action_button"]["show"] = False
1933
1968
 
1934
- return build_config
1969
+ return self.update_input_types(build_config)
1935
1970
 
1936
1971
  def configure_tools(self, composio: Composio, limit: int | None = None) -> list[Tool]:
1937
1972
  if limit is None:
@@ -2039,6 +2074,10 @@ class ComposioBaseComponent(Component):
2039
2074
  final_field_name = field
2040
2075
  if field.endswith("_user_id") and field.startswith(self.app_name):
2041
2076
  final_field_name = "user_id"
2077
+ elif field == f"{self.app_name}_status":
2078
+ final_field_name = "status"
2079
+ elif field == f"{self.app_name}_name":
2080
+ final_field_name = "name"
2042
2081
 
2043
2082
  arguments[final_field_name] = value
2044
2083
 
@@ -304,11 +304,12 @@ class BaseFileComponent(Component, ABC):
304
304
  parts.append(str(data_text))
305
305
  elif isinstance(d.data, dict):
306
306
  # convert the data dict to a readable string
307
- parts.append(orjson.dumps(d.data, default=str).decode())
307
+ parts.append(orjson.dumps(d.data, option=orjson.OPT_INDENT_2, default=str).decode())
308
308
  else:
309
309
  parts.append(str(d))
310
- except (AttributeError, TypeError, ValueError):
310
+ except Exception: # noqa: BLE001
311
311
  # Final fallback - just try to convert to string
312
+ # TODO: Consider downstream error case more. Should this raise an error?
312
313
  parts.append(str(d))
313
314
 
314
315
  return Message(text=sep.join(parts), **metadata)
@@ -6,7 +6,7 @@ from langchain_core.tools import BaseTool, ToolException
6
6
  from typing_extensions import override
7
7
 
8
8
  from lfx.base.flow_processing.utils import build_data_from_result_data, format_flow_output_data
9
- from lfx.helpers.flow import build_schema_from_inputs, get_arg_names, get_flow_inputs, run_flow
9
+ from lfx.helpers import build_schema_from_inputs, get_arg_names, get_flow_inputs, run_flow
10
10
  from lfx.log.logger import logger
11
11
  from lfx.utils.async_helpers import run_until_complete
12
12
 
@@ -5,7 +5,7 @@ from lfx.custom.custom_component.component import Component, get_component_toolk
5
5
  from lfx.field_typing import Tool
6
6
  from lfx.graph.graph.base import Graph
7
7
  from lfx.graph.vertex.base import Vertex
8
- from lfx.helpers.flow import get_flow_inputs
8
+ from lfx.helpers import get_flow_inputs
9
9
  from lfx.inputs.inputs import DropdownInput, InputTypes, MessageInput
10
10
  from lfx.log.logger import logger
11
11
  from lfx.schema.data import Data
lfx/cli/commands.py CHANGED
@@ -85,6 +85,11 @@ def serve_command(
85
85
  cat my_flow.json | lfx serve --stdin
86
86
  echo '{"nodes": [...]}' | lfx serve --stdin
87
87
  """
88
+ # Configure logging with the specified level and import logger
89
+ from lfx.log.logger import configure, logger
90
+
91
+ configure(log_level=log_level)
92
+
88
93
  verbose_print = create_verbose_printer(verbose=verbose)
89
94
 
90
95
  # Validate input sources - exactly one must be provided
@@ -134,11 +139,11 @@ def serve_command(
134
139
  temp_file_to_cleanup = None
135
140
 
136
141
  if flow_json is not None:
137
- verbose_print("Processing inline JSON content...")
142
+ logger.info("Processing inline JSON content...")
138
143
  try:
139
144
  # Validate JSON syntax
140
145
  json_data = json.loads(flow_json)
141
- verbose_print("JSON content is valid")
146
+ logger.info("JSON content is valid")
142
147
 
143
148
  # Create a temporary file with the JSON content
144
149
  with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as temp_file:
@@ -146,7 +151,7 @@ def serve_command(
146
151
  temp_file_to_cleanup = temp_file.name
147
152
 
148
153
  script_path = temp_file_to_cleanup
149
- verbose_print(f"Created temporary file: {script_path}")
154
+ logger.info(f"Created temporary file: {script_path}")
150
155
 
151
156
  except json.JSONDecodeError as e:
152
157
  typer.echo(f"Error: Invalid JSON content: {e}", err=True)
@@ -156,17 +161,17 @@ def serve_command(
156
161
  raise typer.Exit(1) from e
157
162
 
158
163
  elif stdin:
159
- verbose_print("Reading JSON content from stdin...")
164
+ logger.info("Reading JSON content from stdin...")
160
165
  try:
161
166
  # Read all content from stdin
162
167
  stdin_content = sys.stdin.read().strip()
163
168
  if not stdin_content:
164
- verbose_print("Error: No content received from stdin")
169
+ logger.error("No content received from stdin")
165
170
  raise typer.Exit(1)
166
171
 
167
172
  # Validate JSON syntax
168
173
  json_data = json.loads(stdin_content)
169
- verbose_print("JSON content from stdin is valid")
174
+ logger.info("JSON content from stdin is valid")
170
175
 
171
176
  # Create a temporary file with the JSON content
172
177
  with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as temp_file:
@@ -174,7 +179,7 @@ def serve_command(
174
179
  temp_file_to_cleanup = temp_file.name
175
180
 
176
181
  script_path = temp_file_to_cleanup
177
- verbose_print(f"Created temporary file from stdin: {script_path}")
182
+ logger.info(f"Created temporary file from stdin: {script_path}")
178
183
 
179
184
  except json.JSONDecodeError as e:
180
185
  verbose_print(f"Error: Invalid JSON content from stdin: {e}")
@@ -210,10 +215,10 @@ def serve_command(
210
215
  raise typer.Exit(1)
211
216
 
212
217
  # Prepare the graph
213
- verbose_print("Preparing graph for serving...")
218
+ logger.info("Preparing graph for serving...")
214
219
  try:
215
220
  graph.prepare()
216
- verbose_print("Graph prepared successfully")
221
+ logger.info("Graph prepared successfully")
217
222
 
218
223
  # Validate global variables for environment compatibility
219
224
  if check_variables:
@@ -221,12 +226,12 @@ def serve_command(
221
226
 
222
227
  validation_errors = validate_global_variables_for_env(graph)
223
228
  if validation_errors:
224
- verbose_print("Global variable validation failed:")
229
+ logger.error("Global variable validation failed:")
225
230
  for error in validation_errors:
226
- verbose_print(f" - {error}")
231
+ logger.error(f" - {error}")
227
232
  raise typer.Exit(1)
228
233
  else:
229
- verbose_print("Global variable validation skipped")
234
+ logger.info("Global variable validation skipped")
230
235
  except Exception as e:
231
236
  verbose_print(f"✗ Failed to prepare graph: {e}")
232
237
  raise typer.Exit(1) from e