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.
- build_backends/__init__.py +0 -0
- build_backends/main.py +313 -0
- build_backends/main_prd.py +202 -0
- flowfile/__init__.py +71 -0
- flowfile/__main__.py +24 -0
- flowfile-0.2.2.dist-info/LICENSE +21 -0
- flowfile-0.2.2.dist-info/METADATA +225 -0
- flowfile-0.2.2.dist-info/RECORD +171 -0
- flowfile-0.2.2.dist-info/WHEEL +4 -0
- flowfile-0.2.2.dist-info/entry_points.txt +9 -0
- flowfile_core/__init__.py +13 -0
- flowfile_core/auth/__init__.py +0 -0
- flowfile_core/auth/jwt.py +140 -0
- flowfile_core/auth/models.py +40 -0
- flowfile_core/auth/secrets.py +178 -0
- flowfile_core/configs/__init__.py +35 -0
- flowfile_core/configs/flow_logger.py +433 -0
- flowfile_core/configs/node_store/__init__.py +0 -0
- flowfile_core/configs/node_store/nodes.py +98 -0
- flowfile_core/configs/settings.py +120 -0
- flowfile_core/database/__init__.py +0 -0
- flowfile_core/database/connection.py +51 -0
- flowfile_core/database/init_db.py +45 -0
- flowfile_core/database/models.py +41 -0
- flowfile_core/fileExplorer/__init__.py +0 -0
- flowfile_core/fileExplorer/funcs.py +259 -0
- flowfile_core/fileExplorer/utils.py +53 -0
- flowfile_core/flowfile/FlowfileFlow.py +1403 -0
- flowfile_core/flowfile/__init__.py +0 -0
- flowfile_core/flowfile/_extensions/__init__.py +0 -0
- flowfile_core/flowfile/_extensions/real_time_interface.py +51 -0
- flowfile_core/flowfile/analytics/__init__.py +0 -0
- flowfile_core/flowfile/analytics/analytics_processor.py +123 -0
- flowfile_core/flowfile/analytics/graphic_walker.py +60 -0
- flowfile_core/flowfile/analytics/schemas/__init__.py +0 -0
- flowfile_core/flowfile/analytics/utils.py +9 -0
- flowfile_core/flowfile/connection_manager/__init__.py +3 -0
- flowfile_core/flowfile/connection_manager/_connection_manager.py +48 -0
- flowfile_core/flowfile/connection_manager/models.py +10 -0
- flowfile_core/flowfile/database_connection_manager/__init__.py +0 -0
- flowfile_core/flowfile/database_connection_manager/db_connections.py +139 -0
- flowfile_core/flowfile/database_connection_manager/models.py +15 -0
- flowfile_core/flowfile/extensions.py +36 -0
- flowfile_core/flowfile/flow_data_engine/__init__.py +0 -0
- flowfile_core/flowfile/flow_data_engine/create/__init__.py +0 -0
- flowfile_core/flowfile/flow_data_engine/create/funcs.py +146 -0
- flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +1521 -0
- flowfile_core/flowfile/flow_data_engine/flow_file_column/__init__.py +0 -0
- flowfile_core/flowfile/flow_data_engine/flow_file_column/main.py +144 -0
- flowfile_core/flowfile/flow_data_engine/flow_file_column/polars_type.py +24 -0
- flowfile_core/flowfile/flow_data_engine/flow_file_column/utils.py +36 -0
- flowfile_core/flowfile/flow_data_engine/fuzzy_matching/__init__.py +0 -0
- flowfile_core/flowfile/flow_data_engine/fuzzy_matching/prepare_for_fuzzy_match.py +38 -0
- flowfile_core/flowfile/flow_data_engine/fuzzy_matching/settings_validator.py +90 -0
- flowfile_core/flowfile/flow_data_engine/join/__init__.py +1 -0
- flowfile_core/flowfile/flow_data_engine/join/verify_integrity.py +54 -0
- flowfile_core/flowfile/flow_data_engine/pivot_table.py +20 -0
- flowfile_core/flowfile/flow_data_engine/polars_code_parser.py +249 -0
- flowfile_core/flowfile/flow_data_engine/read_excel_tables.py +143 -0
- flowfile_core/flowfile/flow_data_engine/sample_data.py +120 -0
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/__init__.py +1 -0
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/models.py +36 -0
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +503 -0
- flowfile_core/flowfile/flow_data_engine/threaded_processes.py +27 -0
- flowfile_core/flowfile/flow_data_engine/types.py +0 -0
- flowfile_core/flowfile/flow_data_engine/utils.py +212 -0
- flowfile_core/flowfile/flow_node/__init__.py +0 -0
- flowfile_core/flowfile/flow_node/flow_node.py +771 -0
- flowfile_core/flowfile/flow_node/models.py +111 -0
- flowfile_core/flowfile/flow_node/schema_callback.py +70 -0
- flowfile_core/flowfile/handler.py +123 -0
- flowfile_core/flowfile/manage/__init__.py +0 -0
- flowfile_core/flowfile/manage/compatibility_enhancements.py +70 -0
- flowfile_core/flowfile/manage/manage_flowfile.py +0 -0
- flowfile_core/flowfile/manage/open_flowfile.py +136 -0
- flowfile_core/flowfile/setting_generator/__init__.py +2 -0
- flowfile_core/flowfile/setting_generator/setting_generator.py +41 -0
- flowfile_core/flowfile/setting_generator/settings.py +176 -0
- flowfile_core/flowfile/sources/__init__.py +0 -0
- flowfile_core/flowfile/sources/external_sources/__init__.py +3 -0
- flowfile_core/flowfile/sources/external_sources/airbyte_sources/__init__.py +0 -0
- flowfile_core/flowfile/sources/external_sources/airbyte_sources/airbyte.py +159 -0
- flowfile_core/flowfile/sources/external_sources/airbyte_sources/models.py +172 -0
- flowfile_core/flowfile/sources/external_sources/airbyte_sources/settings.py +173 -0
- flowfile_core/flowfile/sources/external_sources/base_class.py +39 -0
- flowfile_core/flowfile/sources/external_sources/custom_external_sources/__init__.py +2 -0
- flowfile_core/flowfile/sources/external_sources/custom_external_sources/exchange_rate.py +0 -0
- flowfile_core/flowfile/sources/external_sources/custom_external_sources/external_source.py +100 -0
- flowfile_core/flowfile/sources/external_sources/custom_external_sources/google_sheet.py +74 -0
- flowfile_core/flowfile/sources/external_sources/custom_external_sources/sample_users.py +29 -0
- flowfile_core/flowfile/sources/external_sources/factory.py +22 -0
- flowfile_core/flowfile/sources/external_sources/sql_source/__init__.py +0 -0
- flowfile_core/flowfile/sources/external_sources/sql_source/models.py +90 -0
- flowfile_core/flowfile/sources/external_sources/sql_source/sql_source.py +328 -0
- flowfile_core/flowfile/sources/external_sources/sql_source/utils.py +379 -0
- flowfile_core/flowfile/util/__init__.py +0 -0
- flowfile_core/flowfile/util/calculate_layout.py +137 -0
- flowfile_core/flowfile/util/execution_orderer.py +141 -0
- flowfile_core/flowfile/utils.py +106 -0
- flowfile_core/main.py +138 -0
- flowfile_core/routes/__init__.py +0 -0
- flowfile_core/routes/auth.py +34 -0
- flowfile_core/routes/logs.py +163 -0
- flowfile_core/routes/public.py +10 -0
- flowfile_core/routes/routes.py +601 -0
- flowfile_core/routes/secrets.py +85 -0
- flowfile_core/run_lock.py +11 -0
- flowfile_core/schemas/__init__.py +0 -0
- flowfile_core/schemas/analysis_schemas/__init__.py +0 -0
- flowfile_core/schemas/analysis_schemas/graphic_walker_schemas.py +118 -0
- flowfile_core/schemas/defaults.py +9 -0
- flowfile_core/schemas/external_sources/__init__.py +0 -0
- flowfile_core/schemas/external_sources/airbyte_schemas.py +20 -0
- flowfile_core/schemas/input_schema.py +477 -0
- flowfile_core/schemas/models.py +193 -0
- flowfile_core/schemas/output_model.py +115 -0
- flowfile_core/schemas/schemas.py +106 -0
- flowfile_core/schemas/transform_schema.py +569 -0
- flowfile_core/secrets/__init__.py +0 -0
- flowfile_core/secrets/secrets.py +64 -0
- flowfile_core/utils/__init__.py +0 -0
- flowfile_core/utils/arrow_reader.py +247 -0
- flowfile_core/utils/excel_file_manager.py +18 -0
- flowfile_core/utils/fileManager.py +45 -0
- flowfile_core/utils/fl_executor.py +38 -0
- flowfile_core/utils/utils.py +8 -0
- flowfile_frame/__init__.py +56 -0
- flowfile_frame/__main__.py +12 -0
- flowfile_frame/adapters.py +17 -0
- flowfile_frame/expr.py +1163 -0
- flowfile_frame/flow_frame.py +2093 -0
- flowfile_frame/group_frame.py +199 -0
- flowfile_frame/join.py +75 -0
- flowfile_frame/selectors.py +242 -0
- flowfile_frame/utils.py +184 -0
- flowfile_worker/__init__.py +55 -0
- flowfile_worker/configs.py +95 -0
- flowfile_worker/create/__init__.py +37 -0
- flowfile_worker/create/funcs.py +146 -0
- flowfile_worker/create/models.py +86 -0
- flowfile_worker/create/pl_types.py +35 -0
- flowfile_worker/create/read_excel_tables.py +110 -0
- flowfile_worker/create/utils.py +84 -0
- flowfile_worker/external_sources/__init__.py +0 -0
- flowfile_worker/external_sources/airbyte_sources/__init__.py +0 -0
- flowfile_worker/external_sources/airbyte_sources/cache_manager.py +161 -0
- flowfile_worker/external_sources/airbyte_sources/main.py +89 -0
- flowfile_worker/external_sources/airbyte_sources/models.py +133 -0
- flowfile_worker/external_sources/airbyte_sources/settings.py +0 -0
- flowfile_worker/external_sources/sql_source/__init__.py +0 -0
- flowfile_worker/external_sources/sql_source/main.py +56 -0
- flowfile_worker/external_sources/sql_source/models.py +72 -0
- flowfile_worker/flow_logger.py +58 -0
- flowfile_worker/funcs.py +327 -0
- flowfile_worker/main.py +108 -0
- flowfile_worker/models.py +95 -0
- flowfile_worker/polars_fuzzy_match/__init__.py +0 -0
- flowfile_worker/polars_fuzzy_match/matcher.py +435 -0
- flowfile_worker/polars_fuzzy_match/models.py +36 -0
- flowfile_worker/polars_fuzzy_match/pre_process.py +213 -0
- flowfile_worker/polars_fuzzy_match/process.py +86 -0
- flowfile_worker/polars_fuzzy_match/utils.py +50 -0
- flowfile_worker/process_manager.py +36 -0
- flowfile_worker/routes.py +440 -0
- flowfile_worker/secrets.py +148 -0
- flowfile_worker/spawner.py +187 -0
- flowfile_worker/utils.py +25 -0
- test_utils/__init__.py +3 -0
- test_utils/postgres/__init__.py +1 -0
- test_utils/postgres/commands.py +109 -0
- 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
|