lfx-nightly 0.1.12.dev38__py3-none-any.whl → 0.1.12.dev39__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.

@@ -0,0 +1,357 @@
1
+ from typing import Any
2
+
3
+ from lfx.custom import Component
4
+ from lfx.io import (
5
+ BoolInput,
6
+ FloatInput,
7
+ HandleInput,
8
+ IntInput,
9
+ MultilineInput,
10
+ Output,
11
+ StrInput,
12
+ TableInput,
13
+ )
14
+ from lfx.schema.data import Data
15
+ from lfx.schema.message import Message
16
+
17
+
18
+ class DynamicCreateDataComponent(Component):
19
+ display_name: str = "Dynamic Create Data"
20
+ description: str = "Dynamically create a Data with a specified number of fields."
21
+ name: str = "DynamicCreateData"
22
+ MAX_FIELDS = 15 # Define a constant for maximum number of fields
23
+ icon = "ListFilter"
24
+
25
+ def __init__(self, **kwargs):
26
+ super().__init__(**kwargs)
27
+
28
+ inputs = [
29
+ TableInput(
30
+ name="form_fields",
31
+ display_name="Input Configuration",
32
+ info=(
33
+ "Define the dynamic form fields. Each row creates a new input field "
34
+ "that can connect to other components."
35
+ ),
36
+ table_schema=[
37
+ {
38
+ "name": "field_name",
39
+ "display_name": "Field Name",
40
+ "type": "str",
41
+ "description": "Name for the field (used as both internal name and display label)",
42
+ },
43
+ {
44
+ "name": "field_type",
45
+ "display_name": "Field Type",
46
+ "type": "str",
47
+ "description": "Type of input field to create",
48
+ "options": ["Text", "Data", "Number", "Handle", "Boolean"],
49
+ "value": "Text",
50
+ },
51
+ ],
52
+ value=[],
53
+ real_time_refresh=True,
54
+ ),
55
+ BoolInput(
56
+ name="include_metadata",
57
+ display_name="Include Metadata",
58
+ info="Include form configuration metadata in the output.",
59
+ value=False,
60
+ advanced=True,
61
+ ),
62
+ ]
63
+
64
+ outputs = [
65
+ Output(display_name="Data", name="form_data", method="process_form"),
66
+ Output(display_name="Message", name="message", method="get_message"),
67
+ ]
68
+
69
+ def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None) -> dict:
70
+ """Update build configuration to add dynamic inputs that can connect to other components."""
71
+ if field_name == "form_fields":
72
+ # Clear existing dynamic inputs from build config
73
+ keys_to_remove = [key for key in build_config if key.startswith("dynamic_")]
74
+ for key in keys_to_remove:
75
+ del build_config[key]
76
+
77
+ # Add dynamic inputs based on table configuration
78
+ # Safety check to ensure field_value is not None and is iterable
79
+ if field_value is None:
80
+ field_value = []
81
+
82
+ for i, field_config in enumerate(field_value):
83
+ # Safety check to ensure field_config is not None
84
+ if field_config is None:
85
+ continue
86
+
87
+ field_name = field_config.get("field_name", f"field_{i}")
88
+ display_name = field_name # Use field_name as display_name
89
+ field_type_option = field_config.get("field_type", "Text")
90
+ default_value = "" # All fields have empty default value
91
+ required = False # All fields are optional by default
92
+ help_text = "" # All fields have empty help text
93
+
94
+ # Map field type options to actual field types and input types
95
+ field_type_mapping = {
96
+ "Text": {"field_type": "multiline", "input_types": ["Text", "Message"]},
97
+ "Data": {"field_type": "data", "input_types": ["Data"]},
98
+ "Number": {"field_type": "number", "input_types": ["Text", "Message"]},
99
+ "Handle": {"field_type": "handle", "input_types": ["Text", "Data", "Message"]},
100
+ "Boolean": {"field_type": "boolean", "input_types": None},
101
+ }
102
+
103
+ field_config_mapped = field_type_mapping.get(
104
+ field_type_option, {"field_type": "text", "input_types": []}
105
+ )
106
+ if not isinstance(field_config_mapped, dict):
107
+ field_config_mapped = {"field_type": "text", "input_types": []}
108
+ field_type = field_config_mapped["field_type"]
109
+ input_types_list = field_config_mapped["input_types"]
110
+
111
+ # Create the appropriate input type based on field_type
112
+ dynamic_input_name = f"dynamic_{field_name}"
113
+
114
+ if field_type == "text":
115
+ if input_types_list:
116
+ build_config[dynamic_input_name] = StrInput(
117
+ name=dynamic_input_name,
118
+ display_name=display_name,
119
+ info=f"{help_text} (Can connect to: {', '.join(input_types_list)})",
120
+ value=default_value,
121
+ required=required,
122
+ input_types=input_types_list,
123
+ )
124
+ else:
125
+ build_config[dynamic_input_name] = StrInput(
126
+ name=dynamic_input_name,
127
+ display_name=display_name,
128
+ info=help_text,
129
+ value=default_value,
130
+ required=required,
131
+ )
132
+
133
+ elif field_type == "multiline":
134
+ if input_types_list:
135
+ build_config[dynamic_input_name] = MultilineInput(
136
+ name=dynamic_input_name,
137
+ display_name=display_name,
138
+ info=f"{help_text} (Can connect to: {', '.join(input_types_list)})",
139
+ value=default_value,
140
+ required=required,
141
+ input_types=input_types_list,
142
+ )
143
+ else:
144
+ build_config[dynamic_input_name] = MultilineInput(
145
+ name=dynamic_input_name,
146
+ display_name=display_name,
147
+ info=help_text,
148
+ value=default_value,
149
+ required=required,
150
+ )
151
+
152
+ elif field_type == "number":
153
+ try:
154
+ default_int = int(default_value) if default_value else 0
155
+ except ValueError:
156
+ default_int = 0
157
+
158
+ if input_types_list:
159
+ build_config[dynamic_input_name] = IntInput(
160
+ name=dynamic_input_name,
161
+ display_name=display_name,
162
+ info=f"{help_text} (Can connect to: {', '.join(input_types_list)})",
163
+ value=default_int,
164
+ required=required,
165
+ input_types=input_types_list,
166
+ )
167
+ else:
168
+ build_config[dynamic_input_name] = IntInput(
169
+ name=dynamic_input_name,
170
+ display_name=display_name,
171
+ info=help_text,
172
+ value=default_int,
173
+ required=required,
174
+ )
175
+
176
+ elif field_type == "float":
177
+ try:
178
+ default_float = float(default_value) if default_value else 0.0
179
+ except ValueError:
180
+ default_float = 0.0
181
+
182
+ if input_types_list:
183
+ build_config[dynamic_input_name] = FloatInput(
184
+ name=dynamic_input_name,
185
+ display_name=display_name,
186
+ info=f"{help_text} (Can connect to: {', '.join(input_types_list)})",
187
+ value=default_float,
188
+ required=required,
189
+ input_types=input_types_list,
190
+ )
191
+ else:
192
+ build_config[dynamic_input_name] = FloatInput(
193
+ name=dynamic_input_name,
194
+ display_name=display_name,
195
+ info=help_text,
196
+ value=default_float,
197
+ required=required,
198
+ )
199
+
200
+ elif field_type == "boolean":
201
+ default_bool = default_value.lower() in ["true", "1", "yes"] if default_value else False
202
+
203
+ # Boolean fields don't use input_types parameter to avoid errors
204
+ build_config[dynamic_input_name] = BoolInput(
205
+ name=dynamic_input_name,
206
+ display_name=display_name,
207
+ info=help_text,
208
+ value=default_bool,
209
+ input_types=[],
210
+ required=required,
211
+ )
212
+
213
+ elif field_type == "handle":
214
+ # HandleInput for generic data connections
215
+ build_config[dynamic_input_name] = HandleInput(
216
+ name=dynamic_input_name,
217
+ display_name=display_name,
218
+ info=f"{help_text} (Accepts: {', '.join(input_types_list) if input_types_list else 'Any'})",
219
+ input_types=input_types_list if input_types_list else ["Data", "Text", "Message"],
220
+ required=required,
221
+ )
222
+
223
+ elif field_type == "data":
224
+ # Specialized for Data type connections
225
+ build_config[dynamic_input_name] = HandleInput(
226
+ name=dynamic_input_name,
227
+ display_name=display_name,
228
+ info=f"{help_text} (Data input)",
229
+ input_types=input_types_list if input_types_list else ["Data"],
230
+ required=required,
231
+ )
232
+
233
+ else:
234
+ # Default to text input for unknown types
235
+ build_config[dynamic_input_name] = StrInput(
236
+ name=dynamic_input_name,
237
+ display_name=display_name,
238
+ info=f"{help_text} (Unknown type '{field_type}', defaulting to text)",
239
+ value=default_value,
240
+ required=required,
241
+ )
242
+
243
+ return build_config
244
+
245
+ def get_dynamic_values(self) -> dict[str, Any]:
246
+ """Extract simple values from all dynamic inputs, handling both manual and connected inputs."""
247
+ dynamic_values = {}
248
+ connection_info = {}
249
+ form_fields = getattr(self, "form_fields", [])
250
+
251
+ for field_config in form_fields:
252
+ # Safety check to ensure field_config is not None
253
+ if field_config is None:
254
+ continue
255
+
256
+ field_name = field_config.get("field_name", "")
257
+ if field_name:
258
+ dynamic_input_name = f"dynamic_{field_name}"
259
+ value = getattr(self, dynamic_input_name, None)
260
+
261
+ # Extract simple values from connections or manual input
262
+ if value is not None:
263
+ try:
264
+ extracted_value = self._extract_simple_value(value)
265
+ dynamic_values[field_name] = extracted_value
266
+
267
+ # Determine connection type for status
268
+ if hasattr(value, "text") and hasattr(value, "timestamp"):
269
+ connection_info[field_name] = "Connected (Message)"
270
+ elif hasattr(value, "data"):
271
+ connection_info[field_name] = "Connected (Data)"
272
+ elif isinstance(value, (str, int, float, bool, list, dict)):
273
+ connection_info[field_name] = "Manual input"
274
+ else:
275
+ connection_info[field_name] = "Connected (Object)"
276
+
277
+ except (AttributeError, TypeError, ValueError):
278
+ # Fallback to string representation if all else fails
279
+ dynamic_values[field_name] = str(value)
280
+ connection_info[field_name] = "Error"
281
+ else:
282
+ # Use empty default value if nothing connected
283
+ dynamic_values[field_name] = ""
284
+ connection_info[field_name] = "Empty default"
285
+
286
+ # Store connection info for status output
287
+ self._connection_info = connection_info
288
+ return dynamic_values
289
+
290
+ def _extract_simple_value(self, value: Any) -> Any:
291
+ """Extract the simplest, most useful value from any input type."""
292
+ # Handle None
293
+ if value is None:
294
+ return None
295
+
296
+ # Handle simple types directly
297
+ if isinstance(value, (str, int, float, bool)):
298
+ return value
299
+
300
+ # Handle lists and tuples - keep simple
301
+ if isinstance(value, (list, tuple)):
302
+ return [self._extract_simple_value(item) for item in value]
303
+
304
+ # Handle dictionaries - keep simple
305
+ if isinstance(value, dict):
306
+ return {str(k): self._extract_simple_value(v) for k, v in value.items()}
307
+
308
+ # Handle Message objects - extract only the text
309
+ if hasattr(value, "text"):
310
+ return str(value.text) if value.text is not None else ""
311
+
312
+ # Handle Data objects - extract the data content
313
+ if hasattr(value, "data") and value.data is not None:
314
+ return self._extract_simple_value(value.data)
315
+
316
+ # For any other object, convert to string
317
+ return str(value)
318
+
319
+ def process_form(self) -> Data:
320
+ """Process all dynamic form inputs and return clean data with just field values."""
321
+ # Get all dynamic values (just the key:value pairs)
322
+ dynamic_values = self.get_dynamic_values()
323
+
324
+ # Update status with connection info
325
+ connected_fields = len([v for v in getattr(self, "_connection_info", {}).values() if "Connected" in v])
326
+ total_fields = len(dynamic_values)
327
+
328
+ self.status = f"Form processed successfully. {connected_fields}/{total_fields} fields connected to components."
329
+
330
+ # Return clean Data object with just the field values
331
+ return Data(data=dynamic_values)
332
+
333
+ def get_message(self) -> Message:
334
+ """Return form data as a formatted text message."""
335
+ # Get all dynamic values
336
+ dynamic_values = self.get_dynamic_values()
337
+
338
+ if not dynamic_values:
339
+ return Message(text="No form data available")
340
+
341
+ # Format as text message
342
+ message_lines = ["📋 Form Data:"]
343
+ message_lines.append("=" * 40)
344
+
345
+ for field_name, value in dynamic_values.items():
346
+ # Use field_name as display_name
347
+ display_name = field_name
348
+
349
+ message_lines.append(f"• {display_name}: {value}")
350
+
351
+ message_lines.append("=" * 40)
352
+ message_lines.append(f"Total fields: {len(dynamic_values)}")
353
+
354
+ message_text = "\n".join(message_lines)
355
+ self.status = f"Message formatted with {len(dynamic_values)} fields"
356
+
357
+ return Message(text=message_text)
lfx/custom/validate.py CHANGED
@@ -348,9 +348,18 @@ def prepare_global_scope(module):
348
348
  for node in imports:
349
349
  for alias in node.names:
350
350
  module_name = alias.name
351
- variable_name = alias.asname or alias.name
352
- # Let importlib.import_module raise its own ModuleNotFoundError with the actual missing module
353
- exec_globals[variable_name] = importlib.import_module(module_name)
351
+ # Import the full module path to ensure submodules are loaded
352
+ module_obj = importlib.import_module(module_name)
353
+
354
+ # Determine the variable name
355
+ if alias.asname:
356
+ # For aliased imports like "import yfinance as yf", use the imported module directly
357
+ variable_name = alias.asname
358
+ exec_globals[variable_name] = module_obj
359
+ else:
360
+ # For dotted imports like "urllib.request", set the variable to the top-level package
361
+ variable_name = module_name.split(".")[0]
362
+ exec_globals[variable_name] = importlib.import_module(variable_name)
354
363
 
355
364
  for node in import_froms:
356
365
  module_names_to_try = [node.module]