Flowfile 0.2.2__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 (171) hide show
  1. build_backends/__init__.py +0 -0
  2. build_backends/main.py +313 -0
  3. build_backends/main_prd.py +202 -0
  4. flowfile/__init__.py +71 -0
  5. flowfile/__main__.py +24 -0
  6. flowfile-0.2.2.dist-info/LICENSE +21 -0
  7. flowfile-0.2.2.dist-info/METADATA +225 -0
  8. flowfile-0.2.2.dist-info/RECORD +171 -0
  9. flowfile-0.2.2.dist-info/WHEEL +4 -0
  10. flowfile-0.2.2.dist-info/entry_points.txt +9 -0
  11. flowfile_core/__init__.py +13 -0
  12. flowfile_core/auth/__init__.py +0 -0
  13. flowfile_core/auth/jwt.py +140 -0
  14. flowfile_core/auth/models.py +40 -0
  15. flowfile_core/auth/secrets.py +178 -0
  16. flowfile_core/configs/__init__.py +35 -0
  17. flowfile_core/configs/flow_logger.py +433 -0
  18. flowfile_core/configs/node_store/__init__.py +0 -0
  19. flowfile_core/configs/node_store/nodes.py +98 -0
  20. flowfile_core/configs/settings.py +120 -0
  21. flowfile_core/database/__init__.py +0 -0
  22. flowfile_core/database/connection.py +51 -0
  23. flowfile_core/database/init_db.py +45 -0
  24. flowfile_core/database/models.py +41 -0
  25. flowfile_core/fileExplorer/__init__.py +0 -0
  26. flowfile_core/fileExplorer/funcs.py +259 -0
  27. flowfile_core/fileExplorer/utils.py +53 -0
  28. flowfile_core/flowfile/FlowfileFlow.py +1403 -0
  29. flowfile_core/flowfile/__init__.py +0 -0
  30. flowfile_core/flowfile/_extensions/__init__.py +0 -0
  31. flowfile_core/flowfile/_extensions/real_time_interface.py +51 -0
  32. flowfile_core/flowfile/analytics/__init__.py +0 -0
  33. flowfile_core/flowfile/analytics/analytics_processor.py +123 -0
  34. flowfile_core/flowfile/analytics/graphic_walker.py +60 -0
  35. flowfile_core/flowfile/analytics/schemas/__init__.py +0 -0
  36. flowfile_core/flowfile/analytics/utils.py +9 -0
  37. flowfile_core/flowfile/connection_manager/__init__.py +3 -0
  38. flowfile_core/flowfile/connection_manager/_connection_manager.py +48 -0
  39. flowfile_core/flowfile/connection_manager/models.py +10 -0
  40. flowfile_core/flowfile/database_connection_manager/__init__.py +0 -0
  41. flowfile_core/flowfile/database_connection_manager/db_connections.py +139 -0
  42. flowfile_core/flowfile/database_connection_manager/models.py +15 -0
  43. flowfile_core/flowfile/extensions.py +36 -0
  44. flowfile_core/flowfile/flow_data_engine/__init__.py +0 -0
  45. flowfile_core/flowfile/flow_data_engine/create/__init__.py +0 -0
  46. flowfile_core/flowfile/flow_data_engine/create/funcs.py +146 -0
  47. flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +1521 -0
  48. flowfile_core/flowfile/flow_data_engine/flow_file_column/__init__.py +0 -0
  49. flowfile_core/flowfile/flow_data_engine/flow_file_column/main.py +144 -0
  50. flowfile_core/flowfile/flow_data_engine/flow_file_column/polars_type.py +24 -0
  51. flowfile_core/flowfile/flow_data_engine/flow_file_column/utils.py +36 -0
  52. flowfile_core/flowfile/flow_data_engine/fuzzy_matching/__init__.py +0 -0
  53. flowfile_core/flowfile/flow_data_engine/fuzzy_matching/prepare_for_fuzzy_match.py +38 -0
  54. flowfile_core/flowfile/flow_data_engine/fuzzy_matching/settings_validator.py +90 -0
  55. flowfile_core/flowfile/flow_data_engine/join/__init__.py +1 -0
  56. flowfile_core/flowfile/flow_data_engine/join/verify_integrity.py +54 -0
  57. flowfile_core/flowfile/flow_data_engine/pivot_table.py +20 -0
  58. flowfile_core/flowfile/flow_data_engine/polars_code_parser.py +249 -0
  59. flowfile_core/flowfile/flow_data_engine/read_excel_tables.py +143 -0
  60. flowfile_core/flowfile/flow_data_engine/sample_data.py +120 -0
  61. flowfile_core/flowfile/flow_data_engine/subprocess_operations/__init__.py +1 -0
  62. flowfile_core/flowfile/flow_data_engine/subprocess_operations/models.py +36 -0
  63. flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +503 -0
  64. flowfile_core/flowfile/flow_data_engine/threaded_processes.py +27 -0
  65. flowfile_core/flowfile/flow_data_engine/types.py +0 -0
  66. flowfile_core/flowfile/flow_data_engine/utils.py +212 -0
  67. flowfile_core/flowfile/flow_node/__init__.py +0 -0
  68. flowfile_core/flowfile/flow_node/flow_node.py +771 -0
  69. flowfile_core/flowfile/flow_node/models.py +111 -0
  70. flowfile_core/flowfile/flow_node/schema_callback.py +70 -0
  71. flowfile_core/flowfile/handler.py +123 -0
  72. flowfile_core/flowfile/manage/__init__.py +0 -0
  73. flowfile_core/flowfile/manage/compatibility_enhancements.py +70 -0
  74. flowfile_core/flowfile/manage/manage_flowfile.py +0 -0
  75. flowfile_core/flowfile/manage/open_flowfile.py +136 -0
  76. flowfile_core/flowfile/setting_generator/__init__.py +2 -0
  77. flowfile_core/flowfile/setting_generator/setting_generator.py +41 -0
  78. flowfile_core/flowfile/setting_generator/settings.py +176 -0
  79. flowfile_core/flowfile/sources/__init__.py +0 -0
  80. flowfile_core/flowfile/sources/external_sources/__init__.py +3 -0
  81. flowfile_core/flowfile/sources/external_sources/airbyte_sources/__init__.py +0 -0
  82. flowfile_core/flowfile/sources/external_sources/airbyte_sources/airbyte.py +159 -0
  83. flowfile_core/flowfile/sources/external_sources/airbyte_sources/models.py +172 -0
  84. flowfile_core/flowfile/sources/external_sources/airbyte_sources/settings.py +173 -0
  85. flowfile_core/flowfile/sources/external_sources/base_class.py +39 -0
  86. flowfile_core/flowfile/sources/external_sources/custom_external_sources/__init__.py +2 -0
  87. flowfile_core/flowfile/sources/external_sources/custom_external_sources/exchange_rate.py +0 -0
  88. flowfile_core/flowfile/sources/external_sources/custom_external_sources/external_source.py +100 -0
  89. flowfile_core/flowfile/sources/external_sources/custom_external_sources/google_sheet.py +74 -0
  90. flowfile_core/flowfile/sources/external_sources/custom_external_sources/sample_users.py +29 -0
  91. flowfile_core/flowfile/sources/external_sources/factory.py +22 -0
  92. flowfile_core/flowfile/sources/external_sources/sql_source/__init__.py +0 -0
  93. flowfile_core/flowfile/sources/external_sources/sql_source/models.py +90 -0
  94. flowfile_core/flowfile/sources/external_sources/sql_source/sql_source.py +328 -0
  95. flowfile_core/flowfile/sources/external_sources/sql_source/utils.py +379 -0
  96. flowfile_core/flowfile/util/__init__.py +0 -0
  97. flowfile_core/flowfile/util/calculate_layout.py +137 -0
  98. flowfile_core/flowfile/util/execution_orderer.py +141 -0
  99. flowfile_core/flowfile/utils.py +106 -0
  100. flowfile_core/main.py +138 -0
  101. flowfile_core/routes/__init__.py +0 -0
  102. flowfile_core/routes/auth.py +34 -0
  103. flowfile_core/routes/logs.py +163 -0
  104. flowfile_core/routes/public.py +10 -0
  105. flowfile_core/routes/routes.py +601 -0
  106. flowfile_core/routes/secrets.py +85 -0
  107. flowfile_core/run_lock.py +11 -0
  108. flowfile_core/schemas/__init__.py +0 -0
  109. flowfile_core/schemas/analysis_schemas/__init__.py +0 -0
  110. flowfile_core/schemas/analysis_schemas/graphic_walker_schemas.py +118 -0
  111. flowfile_core/schemas/defaults.py +9 -0
  112. flowfile_core/schemas/external_sources/__init__.py +0 -0
  113. flowfile_core/schemas/external_sources/airbyte_schemas.py +20 -0
  114. flowfile_core/schemas/input_schema.py +477 -0
  115. flowfile_core/schemas/models.py +193 -0
  116. flowfile_core/schemas/output_model.py +115 -0
  117. flowfile_core/schemas/schemas.py +106 -0
  118. flowfile_core/schemas/transform_schema.py +569 -0
  119. flowfile_core/secrets/__init__.py +0 -0
  120. flowfile_core/secrets/secrets.py +64 -0
  121. flowfile_core/utils/__init__.py +0 -0
  122. flowfile_core/utils/arrow_reader.py +247 -0
  123. flowfile_core/utils/excel_file_manager.py +18 -0
  124. flowfile_core/utils/fileManager.py +45 -0
  125. flowfile_core/utils/fl_executor.py +38 -0
  126. flowfile_core/utils/utils.py +8 -0
  127. flowfile_frame/__init__.py +56 -0
  128. flowfile_frame/__main__.py +12 -0
  129. flowfile_frame/adapters.py +17 -0
  130. flowfile_frame/expr.py +1163 -0
  131. flowfile_frame/flow_frame.py +2093 -0
  132. flowfile_frame/group_frame.py +199 -0
  133. flowfile_frame/join.py +75 -0
  134. flowfile_frame/selectors.py +242 -0
  135. flowfile_frame/utils.py +184 -0
  136. flowfile_worker/__init__.py +55 -0
  137. flowfile_worker/configs.py +95 -0
  138. flowfile_worker/create/__init__.py +37 -0
  139. flowfile_worker/create/funcs.py +146 -0
  140. flowfile_worker/create/models.py +86 -0
  141. flowfile_worker/create/pl_types.py +35 -0
  142. flowfile_worker/create/read_excel_tables.py +110 -0
  143. flowfile_worker/create/utils.py +84 -0
  144. flowfile_worker/external_sources/__init__.py +0 -0
  145. flowfile_worker/external_sources/airbyte_sources/__init__.py +0 -0
  146. flowfile_worker/external_sources/airbyte_sources/cache_manager.py +161 -0
  147. flowfile_worker/external_sources/airbyte_sources/main.py +89 -0
  148. flowfile_worker/external_sources/airbyte_sources/models.py +133 -0
  149. flowfile_worker/external_sources/airbyte_sources/settings.py +0 -0
  150. flowfile_worker/external_sources/sql_source/__init__.py +0 -0
  151. flowfile_worker/external_sources/sql_source/main.py +56 -0
  152. flowfile_worker/external_sources/sql_source/models.py +72 -0
  153. flowfile_worker/flow_logger.py +58 -0
  154. flowfile_worker/funcs.py +327 -0
  155. flowfile_worker/main.py +108 -0
  156. flowfile_worker/models.py +95 -0
  157. flowfile_worker/polars_fuzzy_match/__init__.py +0 -0
  158. flowfile_worker/polars_fuzzy_match/matcher.py +435 -0
  159. flowfile_worker/polars_fuzzy_match/models.py +36 -0
  160. flowfile_worker/polars_fuzzy_match/pre_process.py +213 -0
  161. flowfile_worker/polars_fuzzy_match/process.py +86 -0
  162. flowfile_worker/polars_fuzzy_match/utils.py +50 -0
  163. flowfile_worker/process_manager.py +36 -0
  164. flowfile_worker/routes.py +440 -0
  165. flowfile_worker/secrets.py +148 -0
  166. flowfile_worker/spawner.py +187 -0
  167. flowfile_worker/utils.py +25 -0
  168. test_utils/__init__.py +3 -0
  169. test_utils/postgres/__init__.py +1 -0
  170. test_utils/postgres/commands.py +109 -0
  171. test_utils/postgres/fixtures.py +417 -0
@@ -0,0 +1,601 @@
1
+ import inspect
2
+ import logging
3
+ import os
4
+ from pathlib import Path
5
+ from typing import List, Dict, Any, Optional
6
+ from sqlalchemy.orm import Session
7
+
8
+ from fastapi import APIRouter, File, UploadFile, BackgroundTasks, HTTPException, status, Body, Depends
9
+ from fastapi.responses import JSONResponse, Response
10
+ # External dependencies
11
+ from polars_expr_transformer.function_overview import get_all_expressions, get_expression_overview
12
+
13
+ # Core modules
14
+ from flowfile_core.auth.jwt import get_current_active_user
15
+ from flowfile_core.configs import logger
16
+ from flowfile_core.configs.node_store import nodes
17
+ from flowfile_core.configs.settings import IS_RUNNING_IN_DOCKER
18
+ # File handling
19
+ from flowfile_core.fileExplorer.funcs import (
20
+ FileExplorer,
21
+ FileInfo,
22
+ get_files_from_directory
23
+ )
24
+ from flowfile_core.flowfile.FlowfileFlow import add_connection, delete_connection
25
+ from flowfile_core.flowfile.analytics.analytics_processor import AnalyticsProcessor
26
+ from flowfile_core.flowfile.extensions import get_instant_func_results
27
+ # Flow handling
28
+ from flowfile_core.flowfile.sources.external_sources.airbyte_sources.models import AirbyteConfigTemplate
29
+ # Airbyte
30
+ from flowfile_core.flowfile.sources.external_sources.airbyte_sources.settings import (
31
+ airbyte_config_handler,
32
+ AirbyteHandler
33
+ )
34
+ from flowfile_core.flowfile.sources.external_sources.sql_source.sql_source import create_sql_source_from_db_settings
35
+ from flowfile_core.run_lock import get_flow_run_lock
36
+ # Schema and models
37
+ from flowfile_core.schemas import input_schema, schemas, output_model
38
+ from flowfile_core.utils import excel_file_manager
39
+ from flowfile_core.utils.fileManager import create_dir, remove_paths
40
+ from flowfile_core.utils.utils import camel_case_to_snake_case
41
+ from flowfile_core import flow_file_handler
42
+ from flowfile_core.flowfile.database_connection_manager.db_connections import (store_database_connection,
43
+ get_database_connection,
44
+ delete_database_connection,
45
+ get_all_database_connections_interface)
46
+ from flowfile_core.database.connection import get_db
47
+
48
+
49
+
50
+ # Router setup
51
+ router = APIRouter(dependencies=[Depends(get_current_active_user)])
52
+
53
+ # Initialize services
54
+ file_explorer = FileExplorer('/app/shared' if IS_RUNNING_IN_DOCKER else None)
55
+
56
+
57
+ def get_node_model(setting_name_ref: str):
58
+ for ref_name, ref in inspect.getmodule(input_schema).__dict__.items():
59
+ if ref_name.lower() == setting_name_ref:
60
+ return ref
61
+
62
+
63
+ @router.post("/upload/")
64
+ async def upload_file(file: UploadFile = File(...)):
65
+ file_location = f"uploads/{file.filename}"
66
+ with open(file_location, "wb+") as file_object:
67
+ file_object.write(file.file.read())
68
+ return JSONResponse(content={"filename": file.filename, "filepath": file_location})
69
+
70
+
71
+ @router.get('/files/files_in_local_directory/', response_model=List[FileInfo], tags=['file manager'])
72
+ async def get_local_files(directory: str) -> List[FileInfo]:
73
+ files = get_files_from_directory(directory)
74
+ if files is None:
75
+ raise HTTPException(404, 'Directory does not exist')
76
+ return files
77
+
78
+
79
+ @router.get('/files/tree/', response_model=List[FileInfo], tags=['file manager'])
80
+ async def get_current_files() -> List[FileInfo]:
81
+ f = file_explorer.list_contents()
82
+ return f
83
+
84
+
85
+ @router.post('/files/navigate_up/', response_model=str, tags=['file manager'])
86
+ async def navigate_up() -> str:
87
+ file_explorer.navigate_up()
88
+ return str(file_explorer.current_path)
89
+
90
+
91
+ @router.post('/files/navigate_into/', response_model=str, tags=['file manager'])
92
+ async def navigate_into_directory(directory_name: str) -> str:
93
+ file_explorer.navigate_into(directory_name)
94
+ return str(file_explorer.current_path)
95
+
96
+
97
+ @router.post('/files/navigate_to/', tags=['file manager'])
98
+ async def navigate_to_directory(directory_name: str) -> str:
99
+ file_explorer.navigate_to(directory_name)
100
+ return str(file_explorer.current_path)
101
+
102
+
103
+ @router.get('/files/current_path/', response_model=str, tags=['file manager'])
104
+ async def get_current_path() -> str:
105
+ return str(file_explorer.current_path)
106
+
107
+
108
+ @router.get('/files/directory_contents/', response_model=List[FileInfo], tags=['file manager'])
109
+ async def get_directory_contents(directory: str, file_types: List[str] = None,
110
+ include_hidden: bool = False) -> List[FileInfo]:
111
+ directory_explorer = FileExplorer(directory)
112
+ try:
113
+ return directory_explorer.list_contents(show_hidden=include_hidden, file_types=file_types)
114
+ except Exception as e:
115
+ logger.error(e)
116
+ HTTPException(404, 'Could not access the directory')
117
+
118
+
119
+ @router.get('/files/current_directory_contents/', response_model=List[FileInfo], tags=['file manager'])
120
+ async def get_current_directory_contents(file_types: List[str] = None, include_hidden: bool = False) -> List[FileInfo]:
121
+ return file_explorer.list_contents(file_types=file_types, show_hidden=include_hidden)
122
+
123
+
124
+ @router.post('/files/create_directory', response_model=output_model.OutputDir, tags=['file manager'])
125
+ def create_directory(new_directory: input_schema.NewDirectory) -> bool:
126
+ result, error = create_dir(new_directory)
127
+ if result:
128
+ return True
129
+ else:
130
+ raise error
131
+
132
+
133
+ @router.post('/flow/register/', tags=['editor'])
134
+ def register_flow(flow_data: schemas.FlowSettings):
135
+ return flow_file_handler.register_flow(flow_data)
136
+
137
+
138
+ @router.get('/active_flowfile_sessions/', response_model=List[schemas.FlowSettings])
139
+ async def get_active_flow_file_sessions() -> List[schemas.FlowSettings]:
140
+ return [flf.flow_settings for flf in flow_file_handler.flowfile_flows]
141
+
142
+
143
+ @router.post('/flow/run/', tags=['editor'])
144
+ async def run_flow(flow_id: int, background_tasks: BackgroundTasks):
145
+ logger.info('starting to run...')
146
+ flow = flow_file_handler.get_flow(flow_id)
147
+ lock = get_flow_run_lock(flow_id)
148
+ async with lock:
149
+ if flow.flow_settings.is_running:
150
+ raise HTTPException(422, 'Flow is already running')
151
+ background_tasks.add_task(flow.run_graph)
152
+ JSONResponse(content={"message": "Data started", "flow_id": flow_id}, status_code=status.HTTP_202_ACCEPTED)
153
+
154
+
155
+ @router.post('/flow/cancel/', tags=['editor'])
156
+ def cancel_flow(flow_id: int):
157
+ flow = flow_file_handler.get_flow(flow_id)
158
+ if not flow.flow_settings.is_running:
159
+ raise HTTPException(422, 'Flow is not running')
160
+ flow.cancel()
161
+
162
+
163
+ @router.get('/flow/run_status/', tags=['editor'],
164
+ response_model=output_model.RunInformation)
165
+ def get_run_status(flow_id: int, response: Response):
166
+ flow = flow_file_handler.get_flow(flow_id)
167
+ if not flow:
168
+ raise HTTPException(status_code=404, detail="Flow not found")
169
+ if flow.flow_settings.is_running:
170
+ response.status_code = status.HTTP_202_ACCEPTED
171
+ return flow.get_run_info()
172
+ response.status_code = status.HTTP_200_OK
173
+ return flow.get_run_info()
174
+
175
+
176
+ @router.post('/transform/manual_input', tags=['transform'])
177
+ def add_manual_input(manual_input: input_schema.NodeManualInput):
178
+ flow = flow_file_handler.get_flow(manual_input.flow_id)
179
+ flow.add_datasource(manual_input)
180
+
181
+
182
+ @router.post('/transform/add_input/', tags=['transform'])
183
+ def add_flow_input(input_data: input_schema.NodeDatasource):
184
+ flow = flow_file_handler.get_flow(input_data.flow_id)
185
+ try:
186
+ flow.add_datasource(input_data)
187
+ except:
188
+ input_data.file_ref = os.path.join('db_data', input_data.file_ref)
189
+ flow.add_datasource(input_data)
190
+
191
+
192
+ @router.post('/editor/copy_node', tags=['editor'])
193
+ def copy_node(node_id_to_copy_from: int, flow_id_to_copy_from: int, node_promise: input_schema.NodePromise):
194
+ """
195
+ Add a node to the flow.
196
+ Parameters
197
+ ----------
198
+ node_id_to_copy_from: int, the id of the node to copy
199
+ flow_id_to_copy_from: int, the id of the flow to copy from
200
+ node_promise: NodePromise, the node promise that contains all the data
201
+ Returns
202
+ -------
203
+ """
204
+ try:
205
+ flow_to_copy_from = flow_file_handler.get_flow(flow_id_to_copy_from)
206
+ flow = (flow_to_copy_from
207
+ if flow_id_to_copy_from == node_promise.flow_id
208
+ else flow_file_handler.get_flow(node_promise.flow_id)
209
+ )
210
+ node_to_copy = flow_to_copy_from.get_node(node_id_to_copy_from)
211
+ logger.info(f"Copying data {node_promise.node_type}")
212
+
213
+ if flow.flow_settings.is_running:
214
+ raise HTTPException(422, "Flow is running")
215
+
216
+ if flow.get_node(node_promise.node_id) is not None:
217
+ flow.delete_node(node_promise.node_id)
218
+
219
+ if node_promise.node_type == "explore_data":
220
+ flow.add_initial_node_analysis(node_promise)
221
+ return
222
+
223
+ flow.copy_node(node_promise, node_to_copy.setting_input, node_to_copy.node_type)
224
+
225
+ except Exception as e:
226
+ logger.error(e)
227
+ raise HTTPException(422, str(e))
228
+
229
+
230
+ @router.post('/editor/add_node/', tags=['editor'])
231
+ def add_node(flow_id: int, node_id: int, node_type: str, pos_x: int = 0, pos_y: int = 0):
232
+ """
233
+ Add a node to the flow.
234
+ Parameters
235
+ ----------
236
+ flow_id: int, the flow id
237
+ node_id: int, the node id
238
+ node_type: str, the node type
239
+ pos_x: int, the x position of the node
240
+ pos_y: int, the y position of the node
241
+
242
+ Returns
243
+ -------
244
+
245
+ """
246
+ flow = flow_file_handler.get_flow(flow_id)
247
+ logger.info(f'Adding a promise for {node_type}')
248
+ if flow.flow_settings.is_running:
249
+ raise HTTPException(422, 'Flow is running')
250
+ node = flow.get_node(node_id)
251
+ if node is not None:
252
+ flow.delete_node(node_id)
253
+ node_promise = input_schema.NodePromise(flow_id=flow_id, node_id=node_id, cache_results=False, pos_x=pos_x,
254
+ pos_y=pos_y,
255
+ node_type=node_type)
256
+ if node_type == 'explore_data':
257
+ flow.add_initial_node_analysis(node_promise)
258
+ return
259
+ else:
260
+ logger.info("Adding node")
261
+ flow.add_node_promise(node_promise)
262
+
263
+ if nodes.check_if_has_default_setting(node_type):
264
+ logger.info(f'Found standard settings for {node_type}, trying to upload them')
265
+ setting_name_ref = 'node' + node_type.replace('_', '')
266
+ node_model = get_node_model(setting_name_ref)
267
+ add_func = getattr(flow, 'add_' + node_type)
268
+ initial_settings = node_model(flow_id=flow_id, node_id=node_id, cache_results=False,
269
+ pos_x=pos_x, pos_y=pos_y, node_type=node_type)
270
+ add_func(initial_settings)
271
+
272
+
273
+ @router.post('/editor/delete_node/', tags=['editor'])
274
+ def delete_node(flow_id: Optional[int], node_id: int):
275
+ logger.info('Deleting node')
276
+ flow = flow_file_handler.get_flow(flow_id)
277
+ if flow.flow_settings.is_running:
278
+ raise HTTPException(422, 'Flow is running')
279
+ flow.delete_node(node_id)
280
+
281
+
282
+ @router.post('/editor/delete_connection/', tags=['editor'])
283
+ def delete_node_connection(flow_id: int, node_connection: input_schema.NodeConnection = None):
284
+ flow_id = int(flow_id)
285
+ logger.info(
286
+ f'Deleting connection node {node_connection.output_connection.node_id} to node {node_connection.input_connection.node_id}')
287
+ flow = flow_file_handler.get_flow(flow_id)
288
+ if flow.flow_settings.is_running:
289
+ raise HTTPException(422, 'Flow is running')
290
+ delete_connection(flow, node_connection)
291
+
292
+
293
+ @router.post("/db_connection_lib", tags=['db_connections'])
294
+ def create_db_connection(input_connection: input_schema.FullDatabaseConnection,
295
+ current_user=Depends(get_current_active_user),
296
+ db: Session = Depends(get_db)
297
+ ):
298
+ """
299
+ Create a database connection.
300
+ """
301
+ logger.info(f'Creating database connection {input_connection.connection_name}')
302
+ try:
303
+ store_database_connection(db, input_connection, current_user.id)
304
+ except ValueError:
305
+ raise HTTPException(422, 'Connection name already exists')
306
+ except Exception as e:
307
+ logger.error(e)
308
+ raise HTTPException(422, str(e))
309
+ return {"message": "Database connection created successfully"}
310
+
311
+
312
+ @router.delete('/db_connection_lib', tags=['db_connections'])
313
+ def delete_db_connection(connection_name: str,
314
+ current_user=Depends(get_current_active_user),
315
+ db: Session = Depends(get_db)
316
+ ):
317
+ """
318
+ Delete a database connection.
319
+ """
320
+ logger.info(f'Deleting database connection {connection_name}')
321
+ db_connection = get_database_connection(db, connection_name, current_user.id)
322
+ if db_connection is None:
323
+ raise HTTPException(404, 'Database connection not found')
324
+ delete_database_connection(db, connection_name, current_user.id)
325
+ return {"message": "Database connection deleted successfully"}
326
+
327
+
328
+ @router.get('/db_connection_lib', tags=['db_connections'],
329
+ response_model=List[input_schema.FullDatabaseConnectionInterface])
330
+ def get_db_connections(
331
+ db: Session = Depends(get_db),
332
+ current_user=Depends(get_current_active_user)) -> List[input_schema.FullDatabaseConnectionInterface]:
333
+ return get_all_database_connections_interface(db, current_user.id)
334
+
335
+
336
+ @router.post('/editor/connect_node/', tags=['editor'])
337
+ def connect_node(flow_id: int, node_connection: input_schema.NodeConnection):
338
+ flow = flow_file_handler.get_flow(flow_id)
339
+ if flow is None:
340
+ logger.info('could not find the flow')
341
+ raise HTTPException(404, 'could not find the flow')
342
+ if flow.flow_settings.is_running:
343
+ raise HTTPException(422, 'Flow is running')
344
+ add_connection(flow, node_connection)
345
+
346
+
347
+ @router.get('/editor/expression_doc', tags=['editor'], response_model=List[output_model.ExpressionsOverview])
348
+ def get_expression_doc() -> List[output_model.ExpressionsOverview]:
349
+ return get_expression_overview()
350
+
351
+
352
+ @router.get('/editor/expressions', tags=['editor'], response_model=List[str])
353
+ def get_expressions() -> List[str]:
354
+ return get_all_expressions()
355
+
356
+
357
+ @router.get('/editor/flow', tags=['editor'], response_model=schemas.FlowSettings)
358
+ def get_flow(flow_id: int):
359
+ flow_id = int(flow_id)
360
+ result = get_flow_settings(flow_id)
361
+ return result
362
+
363
+
364
+ @router.post('/editor/create_flow/', tags=['editor'])
365
+ def create_flow(flow_path: str):
366
+ flow_path = Path(flow_path)
367
+ logger.info('Creating flow')
368
+ return flow_file_handler.add_flow(name=flow_path.stem, flow_path=str(flow_path))
369
+
370
+
371
+ @router.post('/editor/close_flow/', tags=['editor'])
372
+ def close_flow(flow_id: int) -> None:
373
+ flow_file_handler.delete_flow(flow_id)
374
+
375
+
376
+ @router.get('/airbyte/available_connectors', tags=['airbyte'])
377
+ def get_available_connectors():
378
+ return airbyte_config_handler.available_connectors
379
+
380
+
381
+ @router.get('/airbyte/available_configs', tags=['airbyte'])
382
+ def get_available_configs() -> List[str]:
383
+ """
384
+ Get the available configurations for the airbyte connectors
385
+ Returns: List of available configurations
386
+ """
387
+ return airbyte_config_handler.available_configs
388
+
389
+
390
+ @router.get('/airbyte/config_template', tags=['airbyte'], response_model=AirbyteConfigTemplate)
391
+ def get_config_spec(connector_name: str):
392
+ a = airbyte_config_handler.get_config('source-' + connector_name)
393
+ return a
394
+
395
+
396
+ @router.post('/airbyte/set_airbyte_configs_for_streams', tags=['airbyte'])
397
+ def set_airbyte_configs_for_streams(airbyte_config: input_schema.AirbyteConfig):
398
+ logger.info('Setting airbyte config, update_style = ')
399
+ logger.info(f'Setting config for {airbyte_config.source_name}')
400
+ logger.debug(f'Config: {airbyte_config.mapped_config_spec}')
401
+ airbyte_handler = AirbyteHandler(airbyte_config=airbyte_config)
402
+ try:
403
+ _ = airbyte_handler.get_available_streams()
404
+ except Exception as e:
405
+ raise HTTPException(404, str(e))
406
+
407
+
408
+ @router.post('/update_settings/', tags=['transform'])
409
+ def add_generic_settings(input_data: Dict[str, Any], node_type: str, current_user=Depends(get_current_active_user)):
410
+ input_data['user_id'] = current_user.id
411
+ node_type = camel_case_to_snake_case(node_type)
412
+ flow_id = int(input_data.get('flow_id'))
413
+ logger.info(f'Updating the data for flow: {flow_id}, node {input_data["node_id"]}')
414
+ flow = flow_file_handler.get_flow(flow_id)
415
+ if flow.flow_settings.is_running:
416
+ raise HTTPException(422, 'Flow is running')
417
+ if flow is None:
418
+ raise HTTPException(404, 'could not find the flow')
419
+ add_func = getattr(flow, 'add_' + node_type)
420
+ parsed_input = None
421
+ setting_name_ref = 'node' + node_type.replace('_', '')
422
+ if add_func is None:
423
+ raise HTTPException(404, 'could not find the function')
424
+ try:
425
+ ref = get_node_model(setting_name_ref)
426
+ if ref:
427
+ parsed_input = ref(**input_data)
428
+ except Exception as e:
429
+ raise HTTPException(421, str(e))
430
+ if parsed_input is None:
431
+ raise HTTPException(404, 'could not find the interface')
432
+ try:
433
+ add_func(parsed_input)
434
+ except Exception as e:
435
+ logger.error(e)
436
+ raise HTTPException(419, str(f'error: {e}'))
437
+
438
+
439
+ @router.get('/files/available_flow_files', tags=['editor'], response_model=List[FileInfo])
440
+ def get_list_of_saved_flows(path: str):
441
+ try:
442
+ return get_files_from_directory(path, types=['flowfile'])
443
+ except:
444
+ return []
445
+
446
+ @router.get('/node_list', response_model=List[nodes.NodeTemplate])
447
+ def get_node_list() -> List[nodes.NodeTemplate]:
448
+ return nodes.nodes_list
449
+
450
+
451
+ # @router.post('/reset')
452
+ # def reset():
453
+ # flow_file_handler.delete_flow(1)
454
+ # register_flow(schemas.FlowSettings(flow_id=1))
455
+
456
+
457
+ @router.post('/files/remove_items', tags=['file manager'])
458
+ def remove_items(remove_items_input: input_schema.RemoveItemsInput):
459
+ result, error = remove_paths(remove_items_input)
460
+ if result:
461
+ return result
462
+ else:
463
+ raise error
464
+
465
+
466
+ @router.get('/node', response_model=output_model.NodeData, tags=['editor'])
467
+ def get_node(flow_id: int, node_id: int, get_data: bool = False):
468
+ logging.info(f'Getting node {node_id} from flow {flow_id}')
469
+ flow = flow_file_handler.get_flow(flow_id)
470
+ node = flow.get_node(node_id)
471
+ if node is None:
472
+ raise HTTPException(422, 'Not found')
473
+ v = node.get_node_data(flow_id=flow.flow_id, include_example=get_data)
474
+ return v
475
+
476
+
477
+ @router.post('/node/description/', tags=['editor'])
478
+ def update_description_node(flow_id: int, node_id: int, description: str = Body(...)):
479
+ try:
480
+ node = flow_file_handler.get_flow(flow_id).get_node(node_id)
481
+ except:
482
+ raise HTTPException(404, 'Could not find the node')
483
+ node.setting_input.description = description
484
+ return True
485
+
486
+
487
+ @router.get('/node/description', tags=['editor'])
488
+ def get_description_node(flow_id: int, node_id: int):
489
+ try:
490
+ node = flow_file_handler.get_flow(flow_id).get_node(node_id)
491
+ except:
492
+ raise HTTPException(404, 'Could not find the node')
493
+ if node is None:
494
+ raise HTTPException(404, 'Could not find the node')
495
+ return node.setting_input.description
496
+
497
+
498
+ @router.get('/node/data', response_model=output_model.TableExample, tags=['editor'])
499
+ def get_table_example(flow_id: int, node_id: int):
500
+ flow = flow_file_handler.get_flow(flow_id)
501
+ node = flow.get_node(node_id)
502
+ return node.get_table_example(True)
503
+
504
+
505
+ @router.get('/node/downstream_node_ids', response_model=List[int], tags=['editor'])
506
+ async def get_downstream_node_ids(flow_id: int, node_id: int) -> List[int]:
507
+ flow = flow_file_handler.get_flow(flow_id)
508
+ node = flow.get_node(node_id)
509
+ return list(node.get_all_dependent_node_ids())
510
+
511
+
512
+ @router.get('/import_flow/', tags=['editor'], response_model=int)
513
+ def import_saved_flow(flow_path: str) -> int:
514
+ flow_path = Path(flow_path)
515
+ if not flow_path.exists():
516
+ raise HTTPException(404, 'File not found')
517
+ return flow_file_handler.import_flow(flow_path)
518
+
519
+
520
+ @router.get('/save_flow', tags=['editor'])
521
+ def save_flow(flow_id: int, flow_path: str = None):
522
+ flow = flow_file_handler.get_flow(flow_id)
523
+ flow.save_flow(flow_path=flow_path)
524
+
525
+
526
+ @router.get('/flow_data', tags=['manager'])
527
+ def get_flow_frontend_data(flow_id: Optional[int] = 1):
528
+ flow = flow_file_handler.get_flow(flow_id)
529
+ if flow is None:
530
+ raise HTTPException(404, 'could not find the flow')
531
+ return flow.get_frontend_data()
532
+
533
+
534
+ @router.get('/flow_settings', tags=['manager'], response_model=schemas.FlowSettings)
535
+ def get_flow_settings(flow_id: Optional[int] = 1) -> schemas.FlowSettings:
536
+ flow = flow_file_handler.get_flow(flow_id)
537
+ if flow is None:
538
+ raise HTTPException(404, 'could not find the flow')
539
+ return flow.flow_settings
540
+
541
+
542
+ @router.post('/flow_settings', tags=['manager'])
543
+ def update_flow_settings(flow_settings: schemas.FlowSettings):
544
+ flow = flow_file_handler.get_flow(flow_settings.flow_id)
545
+ if flow is None:
546
+ raise HTTPException(404, 'could not find the flow')
547
+ flow.flow_settings = flow_settings
548
+
549
+
550
+ @router.get('/flow_data/v2', tags=['manager'])
551
+ def get_vue_flow_data(flow_id: int) -> schemas.VueFlowInput:
552
+ flow = flow_file_handler.get_flow(flow_id)
553
+ if flow is None:
554
+ raise HTTPException(404, 'could not find the flow')
555
+ data = flow.get_vue_flow_input()
556
+ return data
557
+
558
+
559
+ @router.get('/analysis_data/graphic_walker_input', tags=['analysis'], response_model=input_schema.NodeExploreData)
560
+ def get_graphic_walker_input(flow_id: int, node_id: int):
561
+ flow = flow_file_handler.get_flow(flow_id)
562
+ node = flow.get_node(node_id)
563
+ if node.results.analysis_data_generator is None:
564
+ logger.error('The data is not refreshed and available for analysis')
565
+ raise HTTPException(422, 'The data is not refreshed and available for analysis')
566
+ return AnalyticsProcessor.process_graphic_walker_input(node)
567
+
568
+
569
+ @router.get('/custom_functions/instant_result', tags=[])
570
+ async def get_instant_function_result(flow_id: int, node_id: int, func_string: str):
571
+ try:
572
+ node = flow_file_handler.get_node(flow_id, node_id)
573
+ except Exception as e:
574
+ raise HTTPException(404, str(e))
575
+ return get_instant_func_results(node, func_string)
576
+
577
+
578
+ @router.get('/api/get_xlsx_sheet_names', tags=['excel_reader'], response_model=List[str])
579
+ async def get_excel_sheet_names(path: str) -> List[str] | None:
580
+ sheet_names = excel_file_manager.get_sheet_names(path)
581
+ if sheet_names:
582
+ return sheet_names
583
+ else:
584
+ raise HTTPException(404, 'File not found')
585
+
586
+
587
+ @router.post("/validate_db_settings")
588
+ async def validate_db_settings(
589
+ database_settings: input_schema.DatabaseSettings,
590
+ current_user=Depends(get_current_active_user)
591
+ ):
592
+ """
593
+ Validate the query settings for a database connection.
594
+ """
595
+ # Validate the query settings
596
+ try:
597
+ sql_source = create_sql_source_from_db_settings(database_settings, user_id=current_user.id)
598
+ sql_source.validate()
599
+ return {"message": "Query settings are valid"}
600
+ except Exception as e:
601
+ raise HTTPException(status_code=422, detail=str(e))
@@ -0,0 +1,85 @@
1
+ # app_routes/secrets.py
2
+
3
+ import os
4
+ from typing import List
5
+
6
+ from fastapi import APIRouter, Depends, HTTPException
7
+ from sqlalchemy.orm import Session
8
+
9
+ from flowfile_core.auth.jwt import get_current_active_user
10
+ from flowfile_core.auth.models import Secret, SecretInput
11
+ from flowfile_core.database import models as db_models
12
+ from flowfile_core.database.connection import get_db
13
+ from flowfile_core.secrets.secrets import encrypt_secret, store_secret, delete_secret as delete_secret_action
14
+
15
+ router = APIRouter(dependencies=[Depends(get_current_active_user)])
16
+
17
+
18
+ # Get all secrets for current user
19
+ @router.get("/secrets", response_model=List[Secret])
20
+ async def get_secrets(current_user=Depends(get_current_active_user), db: Session = Depends(get_db)):
21
+ user_id = current_user.id
22
+
23
+ # Get secrets from database
24
+ db_secrets = db.query(db_models.Secret).filter(db_models.Secret.user_id == user_id).all()
25
+
26
+ # Decrypt secrets
27
+ secrets = []
28
+ for db_secret in db_secrets:
29
+ secrets.append(Secret(
30
+ name=db_secret.name,
31
+ value=db_secret.encrypted_value,
32
+ user_id=str(db_secret.user_id)
33
+ ))
34
+
35
+ return secrets
36
+
37
+
38
+ # Create a new secret
39
+ @router.post("/secrets", response_model=Secret)
40
+ async def create_secret(secret: SecretInput, current_user=Depends(get_current_active_user),
41
+ db: Session = Depends(get_db)):
42
+ print('current_user', current_user)
43
+ # Get user ID
44
+ user_id = 1 if os.environ.get("FLOWFILE_MODE") == "electron" or 1 == 1 else current_user.id
45
+
46
+ existing_secret = db.query(db_models.Secret).filter(
47
+ db_models.Secret.user_id == user_id,
48
+ db_models.Secret.name == secret.name
49
+ ).first()
50
+
51
+ if existing_secret:
52
+ raise HTTPException(status_code=400, detail="Secret with this name already exists")
53
+
54
+ encrypted_value = store_secret(db, secret, user_id).encrypted_value
55
+ return Secret(name=secret.name, value=encrypted_value, user_id=str(user_id))
56
+
57
+
58
+ # Get a specific secret by name
59
+ @router.get("/secrets/{secret_name}", response_model=Secret)
60
+ async def get_secret(secret_name: str, current_user=Depends(get_current_active_user), db: Session = Depends(get_db)):
61
+ # Get user ID
62
+ user_id = 1 if os.environ.get("FLOWFILE_MODE") == "electron" else current_user.id
63
+
64
+ # Get secret from database
65
+ db_secret = db.query(db_models.Secret).filter(
66
+ db_models.Secret.user_id == user_id,
67
+ db_models.Secret.name == secret_name
68
+ ).first()
69
+
70
+ if not db_secret:
71
+ raise HTTPException(status_code=404, detail="Secret not found")
72
+
73
+ return Secret(
74
+ name=db_secret.name,
75
+ value=db_secret.encrypted_value,
76
+ user_id=str(db_secret.user_id)
77
+ )
78
+
79
+
80
+ @router.delete("/secrets/{secret_name}", status_code=204)
81
+ async def delete_secret(secret_name: str, current_user=Depends(get_current_active_user), db: Session = Depends(get_db)):
82
+ # Get user ID
83
+ user_id = 1 if os.environ.get("FLOWFILE_MODE") == "electron" or 1 == 1 else current_user.id
84
+ delete_secret_action(db, secret_name, user_id)
85
+ return None
@@ -0,0 +1,11 @@
1
+ import asyncio
2
+
3
+
4
+ flow_run_locks = {}
5
+
6
+
7
+ def get_flow_run_lock(flow_id: int) -> asyncio.Lock:
8
+ """Retrieve a lock for the given flow_id, creating it if it doesn't exist."""
9
+ if flow_id not in flow_run_locks:
10
+ flow_run_locks[flow_id] = asyncio.Lock()
11
+ return flow_run_locks[flow_id]
File without changes