Flowfile 0.3.9__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.

Files changed (169) hide show
  1. flowfile/__init__.py +6 -1
  2. flowfile/api.py +0 -1
  3. flowfile/web/static/assets/{CloudConnectionManager-c97c25f8.js → CloudConnectionManager-109ecc3c.js} +2 -2
  4. flowfile/web/static/assets/{CloudStorageReader-f1ff509e.js → CloudStorageReader-19cdd67a.js} +11 -78
  5. flowfile/web/static/assets/{CloudStorageWriter-034f8b78.js → CloudStorageWriter-48e0ae20.js} +12 -79
  6. flowfile/web/static/assets/{CloudStorageWriter-49c9a4b2.css → CloudStorageWriter-b0ee067f.css} +24 -24
  7. flowfile/web/static/assets/ColumnSelector-47996a16.css +10 -0
  8. flowfile/web/static/assets/ColumnSelector-ecaf7c44.js +83 -0
  9. flowfile/web/static/assets/ContextMenu-2b348c4c.js +41 -0
  10. flowfile/web/static/assets/{SettingsSection-9c836ecc.css → ContextMenu-4c74eef1.css} +0 -21
  11. flowfile/web/static/assets/ContextMenu-63cfa99b.css +26 -0
  12. flowfile/web/static/assets/ContextMenu-a779eed7.js +41 -0
  13. flowfile/web/static/assets/ContextMenu-c13f91d0.css +26 -0
  14. flowfile/web/static/assets/ContextMenu-eca26a03.js +41 -0
  15. flowfile/web/static/assets/{CrossJoin-41efa4cb.css → CrossJoin-1119d18e.css} +18 -18
  16. flowfile/web/static/assets/{CrossJoin-9e156ebe.js → CrossJoin-a88f8142.js} +14 -84
  17. flowfile/web/static/assets/CustomNode-74a37f74.css +32 -0
  18. flowfile/web/static/assets/CustomNode-cb863dff.js +211 -0
  19. flowfile/web/static/assets/{DatabaseConnectionSettings-d5c625b3.js → DatabaseConnectionSettings-819d3267.js} +3 -3
  20. flowfile/web/static/assets/{DatabaseManager-265adc5e.js → DatabaseManager-84ee2834.js} +2 -2
  21. flowfile/web/static/assets/{DatabaseReader-0b10551e.js → DatabaseReader-060dd412.js} +14 -114
  22. flowfile/web/static/assets/{DatabaseReader-f50c6558.css → DatabaseReader-ae61773c.css} +0 -27
  23. flowfile/web/static/assets/{DatabaseWriter-c17c6916.js → DatabaseWriter-7fc7750f.js} +13 -74
  24. flowfile/web/static/assets/{ExploreData-5bdae813.css → ExploreData-2d0cf4db.css} +8 -14
  25. flowfile/web/static/assets/ExploreData-82c95991.js +192 -0
  26. flowfile/web/static/assets/{ExternalSource-3a66556c.js → ExternalSource-e1a6ddc7.js} +8 -79
  27. flowfile/web/static/assets/{Filter-91ad87e7.js → Filter-8aca894a.js} +12 -85
  28. flowfile/web/static/assets/{Filter-a9d08ba1.css → Filter-f62091b3.css} +3 -3
  29. flowfile/web/static/assets/{Formula-29f19d21.css → Formula-bb96803d.css} +4 -4
  30. flowfile/web/static/assets/{Formula-3c395ab1.js → Formula-e33686d9.js} +18 -85
  31. flowfile/web/static/assets/{FuzzyMatch-6857de82.css → FuzzyMatch-1010f966.css} +42 -42
  32. flowfile/web/static/assets/{FuzzyMatch-2df0d230.js → FuzzyMatch-abda150d.js} +16 -87
  33. flowfile/web/static/assets/{GraphSolver-d285877f.js → GraphSolver-4ecad1d7.js} +13 -159
  34. flowfile/web/static/assets/GraphSolver-f0cb7bfb.css +22 -0
  35. flowfile/web/static/assets/{GroupBy-0bd1cc6b.js → GroupBy-656d07f3.js} +12 -75
  36. flowfile/web/static/assets/{Unique-b5615727.css → GroupBy-b9505323.css} +8 -8
  37. flowfile/web/static/assets/{Join-5a78a203.js → Join-b84ec849.js} +15 -85
  38. flowfile/web/static/assets/{Join-f45eff22.css → Join-fd79b451.css} +20 -20
  39. flowfile/web/static/assets/{ManualInput-a71b52c6.css → ManualInput-3246a08d.css} +20 -20
  40. flowfile/web/static/assets/{ManualInput-93aef9d6.js → ManualInput-346f4135.js} +11 -82
  41. flowfile/web/static/assets/MultiSelect-61b98268.js +5 -0
  42. flowfile/web/static/assets/MultiSelect.vue_vue_type_script_setup_true_lang-2a7c8312.js +63 -0
  43. flowfile/web/static/assets/NumericInput-e36602c2.js +5 -0
  44. flowfile/web/static/assets/NumericInput.vue_vue_type_script_setup_true_lang-211a1990.js +35 -0
  45. flowfile/web/static/assets/Output-ddc9079f.css +37 -0
  46. flowfile/web/static/assets/{Output-411ecaee.js → Output-eb041599.js} +13 -243
  47. flowfile/web/static/assets/Pivot-cf333e3d.css +22 -0
  48. flowfile/web/static/assets/{Pivot-89db4b04.js → Pivot-f5c774f4.js} +14 -138
  49. flowfile/web/static/assets/PivotValidation-26546cbc.js +61 -0
  50. flowfile/web/static/assets/PivotValidation-891ddfb0.css +13 -0
  51. flowfile/web/static/assets/PivotValidation-c46cd420.css +13 -0
  52. flowfile/web/static/assets/PivotValidation-e150a24b.js +61 -0
  53. flowfile/web/static/assets/{PolarsCode-a9f974f8.js → PolarsCode-da3a7abf.js} +13 -80
  54. flowfile/web/static/assets/Read-0c768769.js +243 -0
  55. flowfile/web/static/assets/Read-6b17491f.css +62 -0
  56. flowfile/web/static/assets/RecordCount-84736276.js +53 -0
  57. flowfile/web/static/assets/{RecordId-55ae7d36.js → RecordId-60055e6d.js} +8 -80
  58. flowfile/web/static/assets/SQLQueryComponent-36cef432.css +27 -0
  59. flowfile/web/static/assets/SQLQueryComponent-8a486004.js +38 -0
  60. flowfile/web/static/assets/{Sample-b4a18476.js → Sample-2d662611.js} +8 -77
  61. flowfile/web/static/assets/{SecretManager-b066d13a.js → SecretManager-ef586cab.js} +2 -2
  62. flowfile/web/static/assets/{Select-727688dc.js → Select-2e4a6965.js} +11 -85
  63. flowfile/web/static/assets/SettingsSection-2e4d03c4.css +21 -0
  64. flowfile/web/static/assets/{SettingsSection-695ac487.js → SettingsSection-310b61c0.js} +2 -40
  65. flowfile/web/static/assets/SettingsSection-5634f439.js +45 -0
  66. flowfile/web/static/assets/SettingsSection-5c696bee.css +20 -0
  67. flowfile/web/static/assets/SettingsSection-71e6b7e3.css +21 -0
  68. flowfile/web/static/assets/SettingsSection-7c68b19f.js +53 -0
  69. flowfile/web/static/assets/SingleSelect-7298811a.js +5 -0
  70. flowfile/web/static/assets/SingleSelect.vue_vue_type_script_setup_true_lang-43807bad.js +62 -0
  71. flowfile/web/static/assets/SliderInput-53105476.js +40 -0
  72. flowfile/web/static/assets/SliderInput-b8fb6a8c.css +4 -0
  73. flowfile/web/static/assets/{GroupBy-ab1ea74b.css → Sort-3643d625.css} +8 -8
  74. flowfile/web/static/assets/{Sort-be3339a8.js → Sort-4fdebe74.js} +12 -97
  75. flowfile/web/static/assets/TextInput-28366b7e.js +5 -0
  76. flowfile/web/static/assets/TextInput.vue_vue_type_script_setup_true_lang-9cad14ba.js +32 -0
  77. flowfile/web/static/assets/{TextToRows-c92d1ec2.css → TextToRows-5d2c1190.css} +9 -9
  78. flowfile/web/static/assets/{TextToRows-7b8998da.js → TextToRows-73ffa692.js} +14 -83
  79. flowfile/web/static/assets/ToggleSwitch-598add30.js +5 -0
  80. flowfile/web/static/assets/ToggleSwitch.vue_vue_type_script_setup_true_lang-f620cd32.js +31 -0
  81. flowfile/web/static/assets/{UnavailableFields-8b0cb48e.js → UnavailableFields-66239e83.js} +2 -2
  82. flowfile/web/static/assets/Union-26b10614.js +77 -0
  83. flowfile/web/static/assets/{Union-8d9ac7f9.css → Union-af6c3d9b.css} +6 -6
  84. flowfile/web/static/assets/{Unique-af5a80b4.js → Unique-33b9edbb.js} +22 -91
  85. flowfile/web/static/assets/{Sort-7ccfa0fe.css → Unique-f9fb0809.css} +8 -8
  86. flowfile/web/static/assets/Unpivot-1e422df3.css +30 -0
  87. flowfile/web/static/assets/{Unpivot-5195d411.js → Unpivot-ef69d0e2.js} +12 -166
  88. flowfile/web/static/assets/UnpivotValidation-0d240eeb.css +13 -0
  89. flowfile/web/static/assets/UnpivotValidation-8658388e.js +51 -0
  90. flowfile/web/static/assets/{ExploreData-18a4fe52.js → VueGraphicWalker-4d7861f4.js} +4 -264
  91. flowfile/web/static/assets/VueGraphicWalker-ed5ab88b.css +6 -0
  92. flowfile/web/static/assets/{api-023d1733.js → api-2d1394bd.js} +1 -1
  93. flowfile/web/static/assets/{api-cb00cce6.js → api-c908fffe.js} +1 -1
  94. flowfile/web/static/assets/{designer-6c322d8e.js → designer-1667687d.js} +2201 -705
  95. flowfile/web/static/assets/{designer-2197d782.css → designer-665e9408.css} +836 -201
  96. flowfile/web/static/assets/{documentation-4d1fafe1.js → documentation-5eed779e.js} +1 -1
  97. flowfile/web/static/assets/{dropDown-0b46dd77.js → dropDown-41ebe3c2.js} +1 -1
  98. flowfile/web/static/assets/{fullEditor-ec4e4f95.js → fullEditor-0670d32d.js} +2 -2
  99. flowfile/web/static/assets/{genericNodeSettings-def5879b.js → genericNodeSettings-38410ebf.js} +3 -3
  100. flowfile/web/static/assets/{index-681a3ed0.css → index-50508d4d.css} +8 -0
  101. flowfile/web/static/assets/{index-683fc198.js → index-5ec791df.js} +210 -31
  102. flowfile/web/static/assets/outputCsv-059583b6.js +86 -0
  103. flowfile/web/static/assets/{Output-48f81019.css → outputCsv-9cc59e0b.css} +0 -143
  104. flowfile/web/static/assets/outputExcel-76b1e02c.js +56 -0
  105. flowfile/web/static/assets/outputExcel-b41305c0.css +102 -0
  106. flowfile/web/static/assets/outputParquet-440fd4c7.js +31 -0
  107. flowfile/web/static/assets/outputParquet-cf8cf3f2.css +4 -0
  108. flowfile/web/static/assets/readCsv-9813903a.js +178 -0
  109. flowfile/web/static/assets/readCsv-bca3ed53.css +52 -0
  110. flowfile/web/static/assets/readExcel-7f40d237.js +203 -0
  111. flowfile/web/static/assets/readExcel-e1b381ea.css +64 -0
  112. flowfile/web/static/assets/readParquet-22d56002.js +26 -0
  113. flowfile/web/static/assets/readParquet-cee068e2.css +19 -0
  114. flowfile/web/static/assets/{secretApi-baceb6f9.js → secretApi-b3cb072e.js} +1 -1
  115. flowfile/web/static/assets/{selectDynamic-de91449a.js → selectDynamic-7ad95bca.js} +7 -7
  116. flowfile/web/static/assets/{selectDynamic-b062bc9b.css → selectDynamic-aa913ff4.css} +16 -16
  117. flowfile/web/static/assets/user-defined-icon-0ae16c90.png +0 -0
  118. flowfile/web/static/assets/{vue-codemirror.esm-dc5e3348.js → vue-codemirror.esm-b1dfaa46.js} +59 -33
  119. flowfile/web/static/assets/{vue-content-loader.es-ba94b82f.js → vue-content-loader.es-22bac17c.js} +1 -1
  120. flowfile/web/static/index.html +2 -2
  121. {flowfile-0.3.9.dist-info → flowfile-0.4.0.dist-info}/METADATA +1 -1
  122. {flowfile-0.3.9.dist-info → flowfile-0.4.0.dist-info}/RECORD +160 -102
  123. flowfile_core/configs/flow_logger.py +5 -13
  124. flowfile_core/configs/node_store/__init__.py +30 -0
  125. flowfile_core/configs/node_store/nodes.py +383 -99
  126. flowfile_core/configs/node_store/user_defined_node_registry.py +193 -0
  127. flowfile_core/configs/settings.py +2 -1
  128. flowfile_core/database/connection.py +5 -21
  129. flowfile_core/fileExplorer/funcs.py +239 -121
  130. flowfile_core/flowfile/flow_data_engine/flow_file_column/interface.py +4 -0
  131. flowfile_core/flowfile/flow_data_engine/flow_file_column/main.py +19 -34
  132. flowfile_core/flowfile/flow_data_engine/flow_file_column/type_registry.py +36 -0
  133. flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +28 -8
  134. flowfile_core/flowfile/flow_graph.py +117 -34
  135. flowfile_core/flowfile/flow_node/flow_node.py +45 -13
  136. flowfile_core/flowfile/handler.py +22 -3
  137. flowfile_core/flowfile/manage/open_flowfile.py +9 -1
  138. flowfile_core/flowfile/node_designer/__init__.py +47 -0
  139. flowfile_core/flowfile/node_designer/_type_registry.py +197 -0
  140. flowfile_core/flowfile/node_designer/custom_node.py +371 -0
  141. flowfile_core/flowfile/node_designer/data_types.py +146 -0
  142. flowfile_core/flowfile/node_designer/ui_components.py +277 -0
  143. flowfile_core/flowfile/schema_callbacks.py +8 -4
  144. flowfile_core/flowfile/setting_generator/settings.py +0 -1
  145. flowfile_core/main.py +5 -1
  146. flowfile_core/routes/routes.py +73 -28
  147. flowfile_core/routes/user_defined_components.py +55 -0
  148. flowfile_core/schemas/input_schema.py +7 -1
  149. flowfile_core/schemas/output_model.py +5 -2
  150. flowfile_core/schemas/schemas.py +8 -3
  151. flowfile_core/schemas/transform_schema.py +1 -0
  152. flowfile_core/utils/validate_setup.py +3 -1
  153. flowfile_worker/__init__.py +6 -35
  154. flowfile_worker/main.py +5 -2
  155. flowfile_worker/routes.py +47 -5
  156. shared/__init__.py +15 -0
  157. shared/storage_config.py +258 -0
  158. flowfile/web/static/assets/GraphSolver-17fd26db.css +0 -68
  159. flowfile/web/static/assets/Pivot-f415e85f.css +0 -35
  160. flowfile/web/static/assets/Read-80dc1675.css +0 -197
  161. flowfile/web/static/assets/Read-c3b1929c.js +0 -701
  162. flowfile/web/static/assets/RecordCount-4e95f98e.js +0 -122
  163. flowfile/web/static/assets/Union-89fd73dc.js +0 -146
  164. flowfile/web/static/assets/Unpivot-246e9bbd.css +0 -77
  165. flowfile/web/static/assets/nodeTitle-a16db7c3.js +0 -227
  166. flowfile/web/static/assets/nodeTitle-f4b12bcb.css +0 -134
  167. {flowfile-0.3.9.dist-info → flowfile-0.4.0.dist-info}/LICENSE +0 -0
  168. {flowfile-0.3.9.dist-info → flowfile-0.4.0.dist-info}/WHEEL +0 -0
  169. {flowfile-0.3.9.dist-info → flowfile-0.4.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,197 @@
1
+ # _type_registry.py - Internal type system (not for public use)
2
+ """
3
+ Internal type registry for mapping between different type representations.
4
+ This module should not be imported directly by users.
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Type, List, Dict, Set, Any, Union
9
+ import polars as pl
10
+
11
+ # Import public types
12
+ from flowfile_core.flowfile.node_designer.data_types import TypeGroup, DataType
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class TypeMapping:
17
+ """Internal mapping between type representations."""
18
+ data_type: DataType
19
+ polars_type: Type[pl.DataType]
20
+ type_group: TypeGroup
21
+ aliases: tuple[str, ...] = ()
22
+
23
+
24
+ class TypeRegistry:
25
+ """
26
+ Internal registry for type conversions and lookups.
27
+ This class is not part of the public API.
28
+ """
29
+
30
+ def __init__(self):
31
+ self._mappings: List[TypeMapping] = [
32
+ # Numeric types
33
+ TypeMapping(DataType.Int8, pl.Int8, TypeGroup.Numeric, ("i8",)),
34
+ TypeMapping(DataType.Int16, pl.Int16, TypeGroup.Numeric, ("i16",)),
35
+ TypeMapping(DataType.Int32, pl.Int32, TypeGroup.Numeric, ("i32", "int32")),
36
+ TypeMapping(DataType.Int64, pl.Int64, TypeGroup.Numeric,
37
+ ("i64", "int64", "int", "integer", "bigint")),
38
+ TypeMapping(DataType.UInt8, pl.UInt8, TypeGroup.Numeric, ("u8",)),
39
+ TypeMapping(DataType.UInt16, pl.UInt16, TypeGroup.Numeric, ("u16",)),
40
+ TypeMapping(DataType.UInt32, pl.UInt32, TypeGroup.Numeric, ("u32", "uint32")),
41
+ TypeMapping(DataType.UInt64, pl.UInt64, TypeGroup.Numeric, ("u64", "uint64")),
42
+ TypeMapping(DataType.Float32, pl.Float32, TypeGroup.Numeric, ("f32", "float32")),
43
+ TypeMapping(DataType.Float64, pl.Float64, TypeGroup.Numeric,
44
+ ("f64", "float64", "float", "double")),
45
+ TypeMapping(DataType.Decimal, pl.Decimal, TypeGroup.Numeric,
46
+ ("decimal", "numeric", "dec")),
47
+
48
+ # String types
49
+ TypeMapping(DataType.String, pl.String, TypeGroup.String,
50
+ ("str", "string", "utf8", "varchar", "text")),
51
+ TypeMapping(DataType.Categorical, pl.Categorical, TypeGroup.String,
52
+ ("cat", "categorical", "enum", "factor")),
53
+
54
+ # Date types
55
+ TypeMapping(DataType.Date, pl.Date, TypeGroup.Date, ("date",)),
56
+ TypeMapping(DataType.Datetime, pl.Datetime, TypeGroup.Date,
57
+ ("datetime", "timestamp")),
58
+ TypeMapping(DataType.Time, pl.Time, TypeGroup.Date, ("time",)),
59
+ TypeMapping(DataType.Duration, pl.Duration, TypeGroup.Date,
60
+ ("duration", "timedelta")),
61
+
62
+ # Other types
63
+ TypeMapping(DataType.Boolean, pl.Boolean, TypeGroup.Boolean,
64
+ ("bool", "boolean")),
65
+ TypeMapping(DataType.Binary, pl.Binary, TypeGroup.Binary,
66
+ ("binary", "bytes", "bytea")),
67
+ TypeMapping(DataType.List, pl.List, TypeGroup.Complex, ("list", "array")),
68
+ TypeMapping(DataType.Struct, pl.Struct, TypeGroup.Complex, ("struct", "object")),
69
+ TypeMapping(DataType.Array, pl.Array, TypeGroup.Complex, ("fixed_array",)),
70
+ ]
71
+
72
+ self._build_indices()
73
+
74
+ def _build_indices(self):
75
+ """Build lookup indices for fast access."""
76
+ self._by_data_type: Dict[DataType, TypeMapping] = {}
77
+ self._by_polars_type: Dict[Type[pl.DataType], TypeMapping] = {}
78
+ self._by_alias: Dict[str, TypeMapping] = {}
79
+ self._by_group: Dict[TypeGroup, List[TypeMapping]] = {g: [] for g in TypeGroup}
80
+
81
+ for mapping in self._mappings:
82
+ self._by_data_type[mapping.data_type] = mapping
83
+ self._by_polars_type[mapping.polars_type] = mapping
84
+
85
+ if mapping.type_group != TypeGroup.All:
86
+ self._by_group[mapping.type_group].append(mapping)
87
+
88
+ # Register all aliases (case-insensitive)
89
+ for alias in mapping.aliases:
90
+ self._by_alias[alias.lower()] = mapping
91
+
92
+ # Register enum names as aliases
93
+ self._by_alias[mapping.data_type.value.lower()] = mapping
94
+ self._by_alias[mapping.polars_type.__name__.lower()] = mapping
95
+
96
+ # Register "pl.TypeName" format
97
+ self._by_alias[f"pl.{mapping.polars_type.__name__}".lower()] = mapping
98
+
99
+ def normalize(self, type_spec: Any) -> Set[DataType]:
100
+ """
101
+ Normalize any type specification to a set of DataType enums.
102
+ This is the main internal API for type resolution.
103
+ """
104
+ # Handle special case: All types
105
+ if type_spec == TypeGroup.All or type_spec == "ALL":
106
+ return set(self._by_data_type.keys())
107
+
108
+ # Handle TypeGroup
109
+ if isinstance(type_spec, TypeGroup):
110
+ return {m.data_type for m in self._by_group.get(type_spec, [])}
111
+
112
+ # Handle DataType
113
+ if isinstance(type_spec, DataType):
114
+ return {type_spec}
115
+
116
+ # Handle Polars type class
117
+ if isinstance(type_spec, type) and issubclass(type_spec, pl.DataType):
118
+ mapping = self._by_polars_type.get(type_spec)
119
+ if mapping:
120
+ return {mapping.data_type}
121
+
122
+ # Handle Polars type instance
123
+ if isinstance(type_spec, pl.DataType):
124
+ base_type = type_spec.base_type() if hasattr(type_spec, 'base_type') else type(type_spec)
125
+ mapping = self._by_polars_type.get(base_type)
126
+ if mapping:
127
+ return {mapping.data_type}
128
+
129
+ # Handle string aliases
130
+ if isinstance(type_spec, str):
131
+ type_spec_lower = type_spec.lower()
132
+
133
+ # Try TypeGroup name
134
+ try:
135
+ group = TypeGroup(type_spec)
136
+ return {m.data_type for m in self._by_group.get(group, [])}
137
+ except (ValueError, KeyError):
138
+ pass
139
+
140
+ # Try DataType name
141
+ try:
142
+ dt = DataType(type_spec)
143
+ return {dt}
144
+ except (ValueError, KeyError):
145
+ pass
146
+
147
+ # Check aliases
148
+ mapping = self._by_alias.get(type_spec_lower)
149
+ if mapping:
150
+ return {mapping.data_type}
151
+
152
+ # Default to empty set if unrecognized
153
+ return set()
154
+
155
+ def normalize_list(self, type_specs: List[Any]) -> Set[DataType]:
156
+ """Normalize a list of type specifications."""
157
+ result = set()
158
+ for spec in type_specs:
159
+ result.update(self.normalize(spec))
160
+ return result
161
+
162
+ def get_polars_types(self, data_types: Set[DataType]) -> Set[Type[pl.DataType]]:
163
+ """Convert a set of DataType enums to Polars types."""
164
+ result = set()
165
+ for dt in data_types:
166
+ mapping = self._by_data_type.get(dt)
167
+ if mapping:
168
+ result.add(mapping.polars_type)
169
+ return result
170
+
171
+ def get_polars_type(self, data_type: DataType) -> Type[pl.DataType]:
172
+ """Get the Polars type for a single DataType."""
173
+ mapping = self._by_data_type.get(data_type)
174
+ return mapping.polars_type if mapping else pl.String # Default fallback
175
+
176
+
177
+ # Singleton instance
178
+ _registry = TypeRegistry()
179
+
180
+
181
+ # Internal API functions (not for public use)
182
+ def normalize_type_spec(type_spec: Any) -> Set[DataType]:
183
+ """Internal function to normalize type specifications."""
184
+ if isinstance(type_spec, list):
185
+ return _registry.normalize_list(type_spec)
186
+ return _registry.normalize(type_spec)
187
+
188
+
189
+ def get_polars_types(data_types: Set[DataType]) -> Set[Type[pl.DataType]]:
190
+ """Internal function to get Polars types."""
191
+ return _registry.get_polars_types(data_types)
192
+
193
+
194
+ def check_column_type(column_dtype: pl.DataType, accepted_types: Set[DataType]) -> bool:
195
+ """Check if a column's dtype matches the accepted types."""
196
+ normalized = _registry.normalize(column_dtype)
197
+ return bool(normalized & accepted_types)
@@ -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
+ )