Flowfile 0.3.10__py3-none-any.whl → 0.4.0__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 Flowfile might be problematic. Click here for more details.
- flowfile/__init__.py +6 -1
- flowfile/web/static/assets/{CloudConnectionManager-d7c2c028.js → CloudConnectionManager-109ecc3c.js} +2 -2
- flowfile/web/static/assets/{CloudStorageReader-d467329f.js → CloudStorageReader-19cdd67a.js} +6 -6
- flowfile/web/static/assets/{CloudStorageWriter-071b8b00.js → CloudStorageWriter-48e0ae20.js} +6 -6
- flowfile/web/static/assets/ColumnSelector-47996a16.css +10 -0
- flowfile/web/static/assets/ColumnSelector-ecaf7c44.js +83 -0
- flowfile/web/static/assets/{ContextMenu-2dea5e27.js → ContextMenu-2b348c4c.js} +1 -1
- flowfile/web/static/assets/{ContextMenu-785554c4.js → ContextMenu-a779eed7.js} +1 -1
- flowfile/web/static/assets/{ContextMenu-a51e19ea.js → ContextMenu-eca26a03.js} +1 -1
- flowfile/web/static/assets/{CrossJoin-cf68ec7a.js → CrossJoin-a88f8142.js} +7 -7
- flowfile/web/static/assets/CustomNode-74a37f74.css +32 -0
- flowfile/web/static/assets/CustomNode-cb863dff.js +211 -0
- flowfile/web/static/assets/{DatabaseConnectionSettings-435c5dd8.js → DatabaseConnectionSettings-819d3267.js} +2 -2
- flowfile/web/static/assets/{DatabaseManager-349e33a8.js → DatabaseManager-84ee2834.js} +2 -2
- flowfile/web/static/assets/{DatabaseReader-8075bd28.js → DatabaseReader-060dd412.js} +9 -9
- flowfile/web/static/assets/{DatabaseWriter-3e2dda89.js → DatabaseWriter-7fc7750f.js} +8 -8
- flowfile/web/static/assets/{ExploreData-76ec698c.js → ExploreData-82c95991.js} +5 -5
- flowfile/web/static/assets/{ExternalSource-609a265c.js → ExternalSource-e1a6ddc7.js} +5 -5
- flowfile/web/static/assets/{Filter-97cff793.js → Filter-8aca894a.js} +7 -7
- flowfile/web/static/assets/{Formula-09de0ec9.js → Formula-e33686d9.js} +7 -7
- flowfile/web/static/assets/{FuzzyMatch-bdf70248.js → FuzzyMatch-abda150d.js} +8 -8
- flowfile/web/static/assets/{GraphSolver-0b5a0e05.js → GraphSolver-4ecad1d7.js} +6 -6
- flowfile/web/static/assets/{GroupBy-eaddadde.js → GroupBy-656d07f3.js} +5 -5
- flowfile/web/static/assets/{Join-3313371b.js → Join-b84ec849.js} +8 -8
- flowfile/web/static/assets/{ManualInput-e8bfc0be.js → ManualInput-346f4135.js} +4 -4
- flowfile/web/static/assets/MultiSelect-61b98268.js +5 -0
- flowfile/web/static/assets/MultiSelect.vue_vue_type_script_setup_true_lang-2a7c8312.js +63 -0
- flowfile/web/static/assets/NumericInput-e36602c2.js +5 -0
- flowfile/web/static/assets/NumericInput.vue_vue_type_script_setup_true_lang-211a1990.js +35 -0
- flowfile/web/static/assets/{Output-7303bb09.js → Output-eb041599.js} +6 -6
- flowfile/web/static/assets/{Pivot-3b1c54ef.js → Pivot-f5c774f4.js} +7 -7
- flowfile/web/static/assets/{PivotValidation-3bb36c8f.js → PivotValidation-26546cbc.js} +1 -1
- flowfile/web/static/assets/{PivotValidation-eaa819c0.js → PivotValidation-e150a24b.js} +1 -1
- flowfile/web/static/assets/{PolarsCode-aa12e25d.js → PolarsCode-da3a7abf.js} +5 -5
- flowfile/web/static/assets/{Read-a2bfc618.js → Read-0c768769.js} +8 -8
- flowfile/web/static/assets/{RecordCount-aa0dc082.js → RecordCount-84736276.js} +4 -4
- flowfile/web/static/assets/{RecordId-48ee1a3b.js → RecordId-60055e6d.js} +6 -6
- flowfile/web/static/assets/{SQLQueryComponent-e149dbf2.js → SQLQueryComponent-8a486004.js} +1 -1
- flowfile/web/static/assets/{Sample-f06cb97a.js → Sample-2d662611.js} +4 -4
- flowfile/web/static/assets/{SecretManager-37f34886.js → SecretManager-ef586cab.js} +2 -2
- flowfile/web/static/assets/{Select-b60e6c47.js → Select-2e4a6965.js} +7 -7
- flowfile/web/static/assets/{SettingsSection-75b6cf4f.js → SettingsSection-310b61c0.js} +1 -1
- flowfile/web/static/assets/{SettingsSection-e57a672e.js → SettingsSection-5634f439.js} +1 -1
- flowfile/web/static/assets/{SettingsSection-70e5a7b1.js → SettingsSection-7c68b19f.js} +1 -1
- flowfile/web/static/assets/SingleSelect-7298811a.js +5 -0
- flowfile/web/static/assets/SingleSelect.vue_vue_type_script_setup_true_lang-43807bad.js +62 -0
- flowfile/web/static/assets/SliderInput-53105476.js +40 -0
- flowfile/web/static/assets/SliderInput-b8fb6a8c.css +4 -0
- flowfile/web/static/assets/{Sort-51b1ee4d.js → Sort-4fdebe74.js} +5 -5
- flowfile/web/static/assets/TextInput-28366b7e.js +5 -0
- flowfile/web/static/assets/TextInput.vue_vue_type_script_setup_true_lang-9cad14ba.js +32 -0
- flowfile/web/static/assets/{TextToRows-26835f8f.js → TextToRows-73ffa692.js} +7 -7
- flowfile/web/static/assets/ToggleSwitch-598add30.js +5 -0
- flowfile/web/static/assets/ToggleSwitch.vue_vue_type_script_setup_true_lang-f620cd32.js +31 -0
- flowfile/web/static/assets/{UnavailableFields-88a4cd0c.js → UnavailableFields-66239e83.js} +2 -2
- flowfile/web/static/assets/{Union-4d0088eb.js → Union-26b10614.js} +4 -4
- flowfile/web/static/assets/{Unique-7d554a62.js → Unique-33b9edbb.js} +7 -7
- flowfile/web/static/assets/{Unpivot-4668595c.js → Unpivot-ef69d0e2.js} +6 -6
- flowfile/web/static/assets/{UnpivotValidation-d4f0e0e8.js → UnpivotValidation-8658388e.js} +1 -1
- flowfile/web/static/assets/{VueGraphicWalker-5324d566.js → VueGraphicWalker-4d7861f4.js} +1 -1
- flowfile/web/static/assets/{api-31e4fea6.js → api-2d1394bd.js} +1 -1
- flowfile/web/static/assets/{api-271ed117.js → api-c908fffe.js} +1 -1
- flowfile/web/static/assets/{designer-bf3d9487.js → designer-1667687d.js} +24 -16
- flowfile/web/static/assets/{designer-091bdc3f.css → designer-665e9408.css} +18 -18
- flowfile/web/static/assets/{documentation-4d0a1cea.js → documentation-5eed779e.js} +1 -1
- flowfile/web/static/assets/{dropDown-025888df.js → dropDown-41ebe3c2.js} +1 -1
- flowfile/web/static/assets/{fullEditor-1df991ec.js → fullEditor-0670d32d.js} +2 -2
- flowfile/web/static/assets/{genericNodeSettings-d3b2b2ac.js → genericNodeSettings-38410ebf.js} +3 -3
- flowfile/web/static/assets/{index-681a3ed0.css → index-50508d4d.css} +8 -0
- flowfile/web/static/assets/{index-d0518598.js → index-5ec791df.js} +6 -6
- flowfile/web/static/assets/{outputCsv-d8457527.js → outputCsv-059583b6.js} +1 -1
- flowfile/web/static/assets/{outputExcel-be89153e.js → outputExcel-76b1e02c.js} +1 -1
- flowfile/web/static/assets/{outputParquet-fabb445a.js → outputParquet-440fd4c7.js} +1 -1
- flowfile/web/static/assets/{readCsv-e8359522.js → readCsv-9813903a.js} +1 -1
- flowfile/web/static/assets/{readExcel-dabaf51b.js → readExcel-7f40d237.js} +3 -3
- flowfile/web/static/assets/{readParquet-e0771ef2.js → readParquet-22d56002.js} +1 -1
- flowfile/web/static/assets/{secretApi-ce823eee.js → secretApi-b3cb072e.js} +1 -1
- flowfile/web/static/assets/{selectDynamic-5476546e.js → selectDynamic-7ad95bca.js} +3 -3
- flowfile/web/static/assets/user-defined-icon-0ae16c90.png +0 -0
- flowfile/web/static/assets/{vue-codemirror.esm-9ed00d50.js → vue-codemirror.esm-b1dfaa46.js} +33 -3
- flowfile/web/static/assets/{vue-content-loader.es-7bca2d9b.js → vue-content-loader.es-22bac17c.js} +1 -1
- flowfile/web/static/index.html +2 -2
- {flowfile-0.3.10.dist-info → flowfile-0.4.0.dist-info}/METADATA +1 -1
- {flowfile-0.3.10.dist-info → flowfile-0.4.0.dist-info}/RECORD +108 -82
- flowfile_core/configs/node_store/__init__.py +30 -0
- flowfile_core/configs/node_store/nodes.py +383 -358
- flowfile_core/configs/node_store/user_defined_node_registry.py +193 -0
- flowfile_core/flowfile/flow_data_engine/flow_file_column/interface.py +4 -0
- flowfile_core/flowfile/flow_data_engine/flow_file_column/main.py +19 -34
- flowfile_core/flowfile/flow_data_engine/flow_file_column/type_registry.py +36 -0
- flowfile_core/flowfile/flow_graph.py +20 -1
- flowfile_core/flowfile/flow_node/flow_node.py +4 -4
- flowfile_core/flowfile/manage/open_flowfile.py +9 -1
- flowfile_core/flowfile/node_designer/__init__.py +47 -0
- flowfile_core/flowfile/node_designer/_type_registry.py +197 -0
- flowfile_core/flowfile/node_designer/custom_node.py +371 -0
- flowfile_core/flowfile/node_designer/data_types.py +146 -0
- flowfile_core/flowfile/node_designer/ui_components.py +277 -0
- flowfile_core/main.py +2 -1
- flowfile_core/routes/routes.py +16 -20
- flowfile_core/routes/user_defined_components.py +55 -0
- flowfile_core/schemas/input_schema.py +8 -1
- flowfile_core/schemas/schemas.py +6 -3
- flowfile_core/utils/validate_setup.py +3 -1
- shared/storage_config.py +17 -2
- {flowfile-0.3.10.dist-info → flowfile-0.4.0.dist-info}/LICENSE +0 -0
- {flowfile-0.3.10.dist-info → flowfile-0.4.0.dist-info}/WHEEL +0 -0
- {flowfile-0.3.10.dist-info → flowfile-0.4.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# Fixed custom_node.py with proper type hints
|
|
2
|
+
|
|
3
|
+
import polars as pl
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
from typing import Any, Dict, Optional, TypeVar, Callable
|
|
6
|
+
from flowfile_core.flowfile.node_designer.ui_components import FlowfileInComponent, IncomingColumns, Section
|
|
7
|
+
from flowfile_core.schemas.schemas import NodeTemplate, NodeTypeLiteral, TransformTypeLiteral
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def to_frontend_schema(model_instance: BaseModel) -> dict:
|
|
11
|
+
"""
|
|
12
|
+
Recursively converts a Pydantic model instance into a JSON-serializable
|
|
13
|
+
dictionary suitable for the frontend.
|
|
14
|
+
|
|
15
|
+
This function handles special marker classes like `IncomingColumns` and
|
|
16
|
+
nested `Section` and `FlowfileInComponent` instances.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
model_instance: The Pydantic model instance to convert.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
A dictionary representation of the model.
|
|
23
|
+
"""
|
|
24
|
+
result = {}
|
|
25
|
+
extra_fields = getattr(model_instance, '__pydantic_extra__', {})
|
|
26
|
+
model_fields = {k: getattr(model_instance, k) for k in model_instance.model_fields.keys()}
|
|
27
|
+
for key, value in (extra_fields|model_fields).items():
|
|
28
|
+
result[key] = _convert_value(value)
|
|
29
|
+
return result
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _convert_value(value: Any) -> Any:
|
|
33
|
+
"""
|
|
34
|
+
Helper function to convert any value to a frontend-ready format.
|
|
35
|
+
"""
|
|
36
|
+
if isinstance(value, Section):
|
|
37
|
+
section_data = value.model_dump(
|
|
38
|
+
include={'title', 'description', 'hidden'},
|
|
39
|
+
exclude_none=True
|
|
40
|
+
)
|
|
41
|
+
section_data["component_type"] = "Section"
|
|
42
|
+
section_data["components"] = {
|
|
43
|
+
key: _convert_value(comp)
|
|
44
|
+
for key, comp in value.get_components().items()
|
|
45
|
+
}
|
|
46
|
+
return section_data
|
|
47
|
+
|
|
48
|
+
elif isinstance(value, FlowfileInComponent):
|
|
49
|
+
component_dict = value.model_dump(exclude_none=True)
|
|
50
|
+
if 'options' in component_dict:
|
|
51
|
+
if component_dict['options'] is IncomingColumns or (
|
|
52
|
+
isinstance(component_dict['options'], type) and
|
|
53
|
+
issubclass(component_dict['options'], IncomingColumns)
|
|
54
|
+
):
|
|
55
|
+
component_dict['options'] = {"__type__": "IncomingColumns"}
|
|
56
|
+
return component_dict
|
|
57
|
+
elif isinstance(value, BaseModel):
|
|
58
|
+
return to_frontend_schema(value)
|
|
59
|
+
elif isinstance(value, list):
|
|
60
|
+
return [_convert_value(item) for item in value]
|
|
61
|
+
elif isinstance(value, dict):
|
|
62
|
+
return {k: _convert_value(v) for k, v in value.items()}
|
|
63
|
+
elif isinstance(value, tuple):
|
|
64
|
+
return tuple(_convert_value(item) for item in value)
|
|
65
|
+
else:
|
|
66
|
+
return value
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# Type variable for the Section factory
|
|
70
|
+
T = TypeVar('T', bound=Section)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def create_section(**components: FlowfileInComponent) -> Section:
|
|
74
|
+
"""
|
|
75
|
+
Factory function to create a Section with proper type hints.
|
|
76
|
+
|
|
77
|
+
This is a convenience function that makes it easier to create `Section`
|
|
78
|
+
objects with autocomplete and type checking in modern editors.
|
|
79
|
+
|
|
80
|
+
Usage:
|
|
81
|
+
advanced_config_section = create_section(
|
|
82
|
+
case_sensitive=case_sensitive_toggle
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
**components: Keyword arguments where each key is the component name
|
|
87
|
+
and the value is a `FlowfileInComponent` instance.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
A new `Section` instance containing the provided components.
|
|
91
|
+
"""
|
|
92
|
+
return Section(**components)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class NodeSettings(BaseModel):
|
|
96
|
+
"""
|
|
97
|
+
The top-level container for all sections in a node's UI.
|
|
98
|
+
|
|
99
|
+
This class holds all the `Section` objects that make up the settings panel
|
|
100
|
+
for a custom node.
|
|
101
|
+
|
|
102
|
+
Example:
|
|
103
|
+
class MyNodeSettings(NodeSettings):
|
|
104
|
+
main_config = main_config_section
|
|
105
|
+
advanced_options = advanced_config_section
|
|
106
|
+
"""
|
|
107
|
+
class Config:
|
|
108
|
+
extra = 'allow'
|
|
109
|
+
arbitrary_types_allowed = True
|
|
110
|
+
|
|
111
|
+
def __init__(self, **sections):
|
|
112
|
+
"""
|
|
113
|
+
Initialize NodeSettings with sections as keyword arguments.
|
|
114
|
+
"""
|
|
115
|
+
super().__init__(**sections)
|
|
116
|
+
|
|
117
|
+
def populate_values(self, values: Dict[str, Any]) -> 'NodeSettings':
|
|
118
|
+
"""
|
|
119
|
+
Populates the settings with values received from the frontend.
|
|
120
|
+
|
|
121
|
+
This method is used internally to update the node's state based on
|
|
122
|
+
user input in the UI.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
values: A dictionary of values from the frontend, where keys are
|
|
126
|
+
section names and values are dictionaries of component
|
|
127
|
+
values.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
The `NodeSettings` instance with updated component values.
|
|
131
|
+
"""
|
|
132
|
+
# Handle both extra fields and defined fields
|
|
133
|
+
all_sections = {}
|
|
134
|
+
|
|
135
|
+
# Get extra fields
|
|
136
|
+
extra_fields = getattr(self, '__pydantic_extra__', {})
|
|
137
|
+
all_sections.update(extra_fields)
|
|
138
|
+
|
|
139
|
+
# Get defined fields that are Sections
|
|
140
|
+
for field_name in self.model_fields:
|
|
141
|
+
field_value = getattr(self, field_name, None)
|
|
142
|
+
if isinstance(field_value, Section):
|
|
143
|
+
all_sections[field_name] = field_value
|
|
144
|
+
|
|
145
|
+
for section_name, section in all_sections.items():
|
|
146
|
+
if section_name in values:
|
|
147
|
+
section_values = values[section_name]
|
|
148
|
+
for component_name, component in section.get_components().items():
|
|
149
|
+
if component_name in section_values:
|
|
150
|
+
component.set_value(section_values[component_name])
|
|
151
|
+
return self
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def create_node_settings(**sections: Section) -> NodeSettings:
|
|
155
|
+
"""
|
|
156
|
+
Factory function to create NodeSettings with proper type hints.
|
|
157
|
+
|
|
158
|
+
This is a convenience function for creating `NodeSettings` instances.
|
|
159
|
+
|
|
160
|
+
Usage:
|
|
161
|
+
FilterNodeSchema = create_node_settings(
|
|
162
|
+
main_config=main_config_section,
|
|
163
|
+
advanced_options=advanced_config_section
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
**sections: Keyword arguments where each key is the section name
|
|
168
|
+
and the value is a `Section` instance.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
A new `NodeSettings` instance containing the provided sections.
|
|
172
|
+
"""
|
|
173
|
+
return NodeSettings(**sections)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class SectionBuilder:
|
|
177
|
+
"""
|
|
178
|
+
A builder pattern for creating `Section` objects with proper type hints.
|
|
179
|
+
|
|
180
|
+
This provides a more fluent and readable way to construct complex sections,
|
|
181
|
+
especially when the number of components is large.
|
|
182
|
+
|
|
183
|
+
Usage:
|
|
184
|
+
builder = SectionBuilder(title="Advanced Settings")
|
|
185
|
+
builder.add_component("timeout", NumericInput(label="Timeout (s)"))
|
|
186
|
+
builder.add_component("retries", NumericInput(label="Number of Retries"))
|
|
187
|
+
advanced_section = builder.build()
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
def __init__(self, title: Optional[str] = None, description: Optional[str] = None, hidden: bool = False):
|
|
191
|
+
self._section = Section(title=title, description=description, hidden=hidden)
|
|
192
|
+
|
|
193
|
+
def add_component(self, name: str, component: FlowfileInComponent) -> 'SectionBuilder':
|
|
194
|
+
"""Add a component to the section."""
|
|
195
|
+
setattr(self._section, name, component)
|
|
196
|
+
extra = getattr(self._section, '__pydantic_extra__', {})
|
|
197
|
+
extra[name] = component
|
|
198
|
+
return self
|
|
199
|
+
|
|
200
|
+
def build(self) -> Section:
|
|
201
|
+
"""Build and return the Section."""
|
|
202
|
+
return self._section
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class NodeSettingsBuilder:
|
|
206
|
+
"""
|
|
207
|
+
A builder pattern for creating `NodeSettings` objects.
|
|
208
|
+
|
|
209
|
+
Provides a fluent interface for constructing the entire settings schema
|
|
210
|
+
for a custom node.
|
|
211
|
+
|
|
212
|
+
Usage:
|
|
213
|
+
settings_builder = NodeSettingsBuilder()
|
|
214
|
+
settings_builder.add_section("main", main_section)
|
|
215
|
+
settings_builder.add_section("advanced", advanced_section)
|
|
216
|
+
my_node_settings = settings_builder.build()
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
def __init__(self):
|
|
220
|
+
self._settings = NodeSettings()
|
|
221
|
+
|
|
222
|
+
def add_section(self, name: str, section: Section) -> 'NodeSettingsBuilder':
|
|
223
|
+
"""Add a section to the node settings."""
|
|
224
|
+
setattr(self._settings, name, section)
|
|
225
|
+
extra = getattr(self._settings, '__pydantic_extra__', {})
|
|
226
|
+
extra[name] = section
|
|
227
|
+
return self
|
|
228
|
+
|
|
229
|
+
def build(self) -> NodeSettings:
|
|
230
|
+
"""Build and return the NodeSettings."""
|
|
231
|
+
return self._settings
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class CustomNodeBase(BaseModel):
|
|
235
|
+
"""
|
|
236
|
+
The base class for creating a custom node in Flowfile.
|
|
237
|
+
|
|
238
|
+
To create a new node, you should inherit from this class and define its
|
|
239
|
+
attributes and the `process` method.
|
|
240
|
+
"""
|
|
241
|
+
# Core node properties
|
|
242
|
+
node_name: str
|
|
243
|
+
node_category: str = "Custom"
|
|
244
|
+
node_icon: str = "user-defined-icon.png"
|
|
245
|
+
settings_schema: Optional[NodeSettings] = None
|
|
246
|
+
|
|
247
|
+
# I/O configuration
|
|
248
|
+
number_of_inputs: int = 1
|
|
249
|
+
number_of_outputs: int = 1
|
|
250
|
+
|
|
251
|
+
# Display properties in the UI
|
|
252
|
+
node_group: Optional[str] = "custom"
|
|
253
|
+
title: Optional[str] = "Custom Node"
|
|
254
|
+
intro: Optional[str] = "A custom node for data processing"
|
|
255
|
+
|
|
256
|
+
# Behavior properties
|
|
257
|
+
node_type: NodeTypeLiteral = "process"
|
|
258
|
+
transform_type: TransformTypeLiteral = "wide"
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
def item(self):
|
|
262
|
+
"""A unique identifier for the node, derived from its name."""
|
|
263
|
+
return self.node_name.replace(" ", "_").lower()
|
|
264
|
+
|
|
265
|
+
class Config:
|
|
266
|
+
arbitrary_types_allowed = True
|
|
267
|
+
|
|
268
|
+
def __init__(self, **data):
|
|
269
|
+
"""
|
|
270
|
+
Initialize the node, optionally populating settings from initial values.
|
|
271
|
+
"""
|
|
272
|
+
initial_values = data.pop('initial_values', None)
|
|
273
|
+
super().__init__(**data)
|
|
274
|
+
if self.settings_schema and initial_values:
|
|
275
|
+
self.settings_schema.populate_values(initial_values)
|
|
276
|
+
|
|
277
|
+
def get_frontend_schema(self) -> dict:
|
|
278
|
+
"""
|
|
279
|
+
Get the frontend-ready schema with current values.
|
|
280
|
+
|
|
281
|
+
This method is called by the backend to send the node's UI definition
|
|
282
|
+
and current state to the frontend.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
A dictionary representing the node's schema and values.
|
|
286
|
+
"""
|
|
287
|
+
schema = {
|
|
288
|
+
"node_name": self.node_name,
|
|
289
|
+
"node_category": self.node_category,
|
|
290
|
+
"node_icon": self.node_icon,
|
|
291
|
+
"number_of_inputs": self.number_of_inputs,
|
|
292
|
+
"number_of_outputs": self.number_of_outputs,
|
|
293
|
+
"node_group": self.node_group,
|
|
294
|
+
"title": self.title,
|
|
295
|
+
"intro": self.intro,
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if self.settings_schema:
|
|
299
|
+
schema["settings_schema"] = to_frontend_schema(self.settings_schema)
|
|
300
|
+
else:
|
|
301
|
+
schema["settings_schema"] = {}
|
|
302
|
+
|
|
303
|
+
return schema
|
|
304
|
+
|
|
305
|
+
@classmethod
|
|
306
|
+
def from_frontend_schema(cls, schema: dict) -> 'CustomNodeBase':
|
|
307
|
+
"""
|
|
308
|
+
Create a node instance from a frontend schema.
|
|
309
|
+
|
|
310
|
+
This is used when loading a node from a saved flow.
|
|
311
|
+
"""
|
|
312
|
+
settings_values = schema.pop('settings_schema', {})
|
|
313
|
+
node = cls(**schema)
|
|
314
|
+
if settings_values and node.settings_schema:
|
|
315
|
+
node.settings_schema.populate_values(settings_values)
|
|
316
|
+
return node
|
|
317
|
+
|
|
318
|
+
@classmethod
|
|
319
|
+
def from_settings(cls, settings_values: dict) -> 'CustomNodeBase':
|
|
320
|
+
"""
|
|
321
|
+
Create a node instance with just its settings values.
|
|
322
|
+
|
|
323
|
+
Useful for creating a configured node instance programmatically.
|
|
324
|
+
"""
|
|
325
|
+
node = cls()
|
|
326
|
+
if settings_values and node.settings_schema:
|
|
327
|
+
node.settings_schema.populate_values(settings_values)
|
|
328
|
+
return node
|
|
329
|
+
|
|
330
|
+
def update_settings(self, values: Dict[str, Any]) -> 'CustomNodeBase':
|
|
331
|
+
"""
|
|
332
|
+
Update the settings with new values from the frontend.
|
|
333
|
+
"""
|
|
334
|
+
if self.settings_schema:
|
|
335
|
+
self.settings_schema.populate_values(values)
|
|
336
|
+
return self
|
|
337
|
+
|
|
338
|
+
def process(self, *inputs: pl.DataFrame) -> pl.DataFrame:
|
|
339
|
+
"""
|
|
340
|
+
The main data processing logic for the node.
|
|
341
|
+
|
|
342
|
+
This method must be implemented by all subclasses. It receives one or
|
|
343
|
+
more Polars DataFrames as input and should return a single DataFrame
|
|
344
|
+
as output.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
*inputs: A variable number of Polars DataFrames, corresponding to
|
|
348
|
+
the inputs connected to the node.
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
A Polars DataFrame containing the processed data.
|
|
352
|
+
"""
|
|
353
|
+
raise NotImplementedError
|
|
354
|
+
|
|
355
|
+
def to_node_template(self) -> NodeTemplate:
|
|
356
|
+
"""
|
|
357
|
+
Convert the node to a `NodeTemplate` for storage or transmission.
|
|
358
|
+
"""
|
|
359
|
+
return NodeTemplate(
|
|
360
|
+
name=self.node_name,
|
|
361
|
+
item=self.item,
|
|
362
|
+
input=self.number_of_inputs,
|
|
363
|
+
output=self.number_of_outputs,
|
|
364
|
+
image=self.node_icon,
|
|
365
|
+
node_group=self.node_group,
|
|
366
|
+
drawer_title=self.title,
|
|
367
|
+
drawer_intro=self.intro,
|
|
368
|
+
node_type=self.node_type,
|
|
369
|
+
transform_type=self.transform_type,
|
|
370
|
+
custom_node=True
|
|
371
|
+
)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# types.py - Public API for type specifications
|
|
2
|
+
"""
|
|
3
|
+
Public type system for column selection and data type specification.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
from flowfile_core.types import Types
|
|
7
|
+
|
|
8
|
+
# Use type groups
|
|
9
|
+
ColumnSelector(data_types=Types.Numeric)
|
|
10
|
+
ColumnSelector(data_types=Types.String)
|
|
11
|
+
|
|
12
|
+
# Use specific types
|
|
13
|
+
ColumnSelector(data_types=Types.Int64)
|
|
14
|
+
ColumnSelector(data_types=Types.Float)
|
|
15
|
+
|
|
16
|
+
# Mix and match
|
|
17
|
+
ColumnSelector(data_types=[Types.Numeric, Types.String])
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from enum import Enum
|
|
21
|
+
from typing import List, Union
|
|
22
|
+
import polars as pl
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TypeGroup(str, Enum):
|
|
26
|
+
"""High-level type groups for column selection."""
|
|
27
|
+
Numeric = "Numeric"
|
|
28
|
+
String = "String"
|
|
29
|
+
Date = "Date"
|
|
30
|
+
Boolean = "Boolean"
|
|
31
|
+
Binary = "Binary"
|
|
32
|
+
Complex = "Complex"
|
|
33
|
+
All = "ALL"
|
|
34
|
+
|
|
35
|
+
def __str__(self) -> str:
|
|
36
|
+
return self.value
|
|
37
|
+
|
|
38
|
+
def __repr__(self) -> str:
|
|
39
|
+
return f"Types.{self.name}"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class DataType(str, Enum):
|
|
43
|
+
"""Specific data types for fine-grained control."""
|
|
44
|
+
# Numeric types
|
|
45
|
+
Int8 = "Int8"
|
|
46
|
+
Int16 = "Int16"
|
|
47
|
+
Int32 = "Int32"
|
|
48
|
+
Int64 = "Int64"
|
|
49
|
+
UInt8 = "UInt8"
|
|
50
|
+
UInt16 = "UInt16"
|
|
51
|
+
UInt32 = "UInt32"
|
|
52
|
+
UInt64 = "UInt64"
|
|
53
|
+
Float32 = "Float32"
|
|
54
|
+
Float64 = "Float64"
|
|
55
|
+
Decimal = "Decimal"
|
|
56
|
+
|
|
57
|
+
# String types
|
|
58
|
+
String = "String"
|
|
59
|
+
Categorical = "Categorical"
|
|
60
|
+
|
|
61
|
+
# Date types
|
|
62
|
+
Date = "Date"
|
|
63
|
+
Datetime = "Datetime"
|
|
64
|
+
Time = "Time"
|
|
65
|
+
Duration = "Duration"
|
|
66
|
+
|
|
67
|
+
# Other types
|
|
68
|
+
Boolean = "Boolean"
|
|
69
|
+
Binary = "Binary"
|
|
70
|
+
List = "List"
|
|
71
|
+
Struct = "Struct"
|
|
72
|
+
Array = "Array"
|
|
73
|
+
|
|
74
|
+
def __str__(self) -> str:
|
|
75
|
+
return self.value
|
|
76
|
+
|
|
77
|
+
def __repr__(self) -> str:
|
|
78
|
+
return f"Types.{self.name}"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class Types:
|
|
82
|
+
"""
|
|
83
|
+
Main entry point for type specifications.
|
|
84
|
+
|
|
85
|
+
Examples:
|
|
86
|
+
Types.Numeric # All numeric columns
|
|
87
|
+
Types.String # All string columns
|
|
88
|
+
Types.Int64 # 64-bit integers only
|
|
89
|
+
Types.Float # Alias for Float64
|
|
90
|
+
Types.All # All column types
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
# Type groups (most common use case)
|
|
94
|
+
Numeric = TypeGroup.Numeric
|
|
95
|
+
String = TypeGroup.String
|
|
96
|
+
Date = TypeGroup.Date
|
|
97
|
+
Boolean = TypeGroup.Boolean
|
|
98
|
+
Binary = TypeGroup.Binary
|
|
99
|
+
Complex = TypeGroup.Complex
|
|
100
|
+
All = TypeGroup.All
|
|
101
|
+
|
|
102
|
+
# Specific numeric types
|
|
103
|
+
Int = DataType.Int64 # Default integer
|
|
104
|
+
Int8 = DataType.Int8
|
|
105
|
+
Int16 = DataType.Int16
|
|
106
|
+
Int32 = DataType.Int32
|
|
107
|
+
Int64 = DataType.Int64
|
|
108
|
+
UInt8 = DataType.UInt8
|
|
109
|
+
UInt16 = DataType.UInt16
|
|
110
|
+
UInt32 = DataType.UInt32
|
|
111
|
+
UInt64 = DataType.UInt64
|
|
112
|
+
|
|
113
|
+
Float = DataType.Float64 # Default float
|
|
114
|
+
Float32 = DataType.Float32
|
|
115
|
+
Float64 = DataType.Float64
|
|
116
|
+
Decimal = DataType.Decimal
|
|
117
|
+
|
|
118
|
+
# String types
|
|
119
|
+
Str = DataType.String
|
|
120
|
+
Text = DataType.String # Alias
|
|
121
|
+
Categorical = DataType.Categorical
|
|
122
|
+
Cat = DataType.Categorical # Short alias
|
|
123
|
+
|
|
124
|
+
# Date/time types
|
|
125
|
+
Date = DataType.Date
|
|
126
|
+
Datetime = DataType.Datetime
|
|
127
|
+
Time = DataType.Time
|
|
128
|
+
Duration = DataType.Duration
|
|
129
|
+
|
|
130
|
+
# Other types
|
|
131
|
+
Bool = DataType.Boolean
|
|
132
|
+
Bytes = DataType.Binary
|
|
133
|
+
List = DataType.List
|
|
134
|
+
Struct = DataType.Struct
|
|
135
|
+
Array = DataType.Array
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# Type alias for better type hints
|
|
139
|
+
TypeSpec = Union[
|
|
140
|
+
TypeGroup,
|
|
141
|
+
DataType,
|
|
142
|
+
str,
|
|
143
|
+
List[Union[TypeGroup, DataType, str, type[pl.DataType], pl.DataType]],
|
|
144
|
+
type[pl.DataType],
|
|
145
|
+
pl.DataType
|
|
146
|
+
]
|