Flowfile 0.5.3__py3-none-any.whl → 0.5.6__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.
- flowfile/__init__.py +16 -0
- flowfile/__main__.py +94 -1
- flowfile/web/static/assets/{AdminView-49392a9a.js → AdminView-c2c7942b.js} +1 -1
- flowfile/web/static/assets/{CloudConnectionView-f13f202b.js → CloudConnectionView-7a3042c6.js} +4 -4
- flowfile/web/static/assets/{CloudConnectionView-36bcd6df.css → CloudConnectionView-cf85f943.css} +17 -17
- flowfile/web/static/assets/{CloudStorageReader-0023d4a5.js → CloudStorageReader-709c4037.js} +8 -8
- flowfile/web/static/assets/{CloudStorageWriter-8e781e11.js → CloudStorageWriter-604c51a8.js} +8 -8
- flowfile/web/static/assets/ColumnActionInput-c44b7aee.css +159 -0
- flowfile/web/static/assets/ColumnActionInput-d63d6746.js +330 -0
- flowfile/web/static/assets/{ColumnSelector-8ad68ea9.js → ColumnSelector-0c8cd1cd.js} +1 -1
- flowfile/web/static/assets/ContextMenu-366bf1b4.js +9 -0
- flowfile/web/static/assets/ContextMenu-85cf5b44.js +9 -0
- flowfile/web/static/assets/ContextMenu-9d28ae6d.js +9 -0
- flowfile/web/static/assets/ContextMenu.vue_vue_type_script_setup_true_lang-774c517c.js +59 -0
- flowfile/web/static/assets/{CrossJoin-03df6938.js → CrossJoin-38e5b99a.js} +9 -9
- flowfile/web/static/assets/{CustomNode-8479239b.js → CustomNode-76e8f3f5.js} +27 -20
- flowfile/web/static/assets/CustomNode-edb9b939.css +42 -0
- flowfile/web/static/assets/{DatabaseConnectionSettings-869e3efd.js → DatabaseConnectionSettings-38155669.js} +4 -4
- flowfile/web/static/assets/{DatabaseConnectionSettings-e91df89a.css → DatabaseConnectionSettings-c20a1e16.css} +22 -20
- flowfile/web/static/assets/{DatabaseReader-c58b9552.js → DatabaseReader-2e549c8f.js} +13 -13
- flowfile/web/static/assets/{DatabaseReader-36898a00.css → DatabaseReader-5bf8c75b.css} +39 -44
- flowfile/web/static/assets/{DatabaseView-d26a9140.js → DatabaseView-dc877c29.js} +2 -2
- flowfile/web/static/assets/{DatabaseWriter-217a99f1.css → DatabaseWriter-bdcf2c8b.css} +27 -25
- flowfile/web/static/assets/{DatabaseWriter-4d05ddc7.js → DatabaseWriter-ffb91864.js} +12 -12
- flowfile/web/static/assets/{DesignerView-a6d0ee84.css → DesignerView-71d4e9a1.css} +429 -376
- flowfile/web/static/assets/{DesignerView-e6f5c0e8.js → DesignerView-a4466dab.js} +338 -183
- flowfile/web/static/assets/{DocumentationView-2e78ef1b.js → DocumentationView-979afc84.js} +3 -3
- flowfile/web/static/assets/{DocumentationView-fd46c656.css → DocumentationView-9ea6e871.css} +9 -9
- flowfile/web/static/assets/{ExploreData-7b54caca.js → ExploreData-e4b92aaf.js} +7 -7
- flowfile/web/static/assets/{ExternalSource-47ab05a3.css → ExternalSource-7ac7373f.css} +17 -17
- flowfile/web/static/assets/{ExternalSource-3fa399b2.js → ExternalSource-d08e7227.js} +9 -9
- flowfile/web/static/assets/{Filter-8cbbdbf3.js → Filter-7add806d.js} +9 -9
- flowfile/web/static/assets/{Formula-aac42b1e.js → Formula-36ab24d2.js} +9 -9
- flowfile/web/static/assets/{FuzzyMatch-cd9bbfca.js → FuzzyMatch-cc01bb04.js} +10 -10
- flowfile/web/static/assets/{GraphSolver-c24dec17.css → GraphSolver-4b4d7db9.css} +4 -4
- flowfile/web/static/assets/{GraphSolver-c7e6780e.js → GraphSolver-4fb98f3b.js} +11 -11
- flowfile/web/static/assets/GroupBy-5792782d.css +9 -0
- flowfile/web/static/assets/{GroupBy-93c5d22b.js → GroupBy-b3c8f429.js} +9 -9
- flowfile/web/static/assets/{Join-a19b2de2.js → Join-096b7b26.js} +10 -10
- flowfile/web/static/assets/{LoginView-0df4ed0a.js → LoginView-c33a246a.js} +1 -1
- flowfile/web/static/assets/{ManualInput-3702e677.css → ManualInput-39111f19.css} +48 -48
- flowfile/web/static/assets/{ManualInput-8d3374b2.js → ManualInput-7307e9b1.js} +55 -13
- flowfile/web/static/assets/{MultiSelect-ad1b6243.js → MultiSelect-14822c48.js} +2 -2
- flowfile/web/static/assets/{MultiSelect.vue_vue_type_script_setup_true_lang-e278950d.js → MultiSelect.vue_vue_type_script_setup_true_lang-90c4d340.js} +1 -1
- flowfile/web/static/assets/{NodeDesigner-40b647c9.js → NodeDesigner-5036c392.js} +171 -69
- flowfile/web/static/assets/{NodeDesigner-5f53be3f.css → NodeDesigner-94cd4dd3.css} +190 -190
- flowfile/web/static/assets/{NumericInput-7100234c.js → NumericInput-15cf3b72.js} +2 -2
- flowfile/web/static/assets/{NumericInput.vue_vue_type_script_setup_true_lang-5130219f.js → NumericInput.vue_vue_type_script_setup_true_lang-91e679d7.js} +1 -1
- flowfile/web/static/assets/{Output-f5efd2aa.js → Output-1f8ed42c.js} +13 -12
- flowfile/web/static/assets/{Output-35e97000.css → Output-692dd25d.css} +10 -10
- flowfile/web/static/assets/{Pivot-d981d23c.js → Pivot-0e153f4e.js} +10 -10
- flowfile/web/static/assets/{PivotValidation-63de1f73.js → PivotValidation-5a4f7c79.js} +1 -1
- flowfile/web/static/assets/{PivotValidation-39386e95.js → PivotValidation-81ec2a33.js} +1 -1
- flowfile/web/static/assets/{PolarsCode-f9d69217.js → PolarsCode-a39f15ac.js} +7 -7
- flowfile/web/static/assets/PopOver-ddcfe4f6.js +138 -0
- flowfile/web/static/assets/{Read-aec2e377.js → Read-39b63932.js} +15 -14
- flowfile/web/static/assets/{Read-36e7bd51.css → Read-90f366bc.css} +13 -13
- flowfile/web/static/assets/{RecordCount-78ed6845.js → RecordCount-e9048ccd.js} +6 -6
- flowfile/web/static/assets/{RecordId-2156e890.js → RecordId-ad02521d.js} +9 -9
- flowfile/web/static/assets/{SQLQueryComponent-48c72f5b.js → SQLQueryComponent-2eeecf0b.js} +3 -3
- flowfile/web/static/assets/SQLQueryComponent-edb90b98.css +29 -0
- flowfile/web/static/assets/{Sample-1352ca74.js → Sample-9a68c23d.js} +6 -6
- flowfile/web/static/assets/{SecretSelector-22b5ff89.js → SecretSelector-2429f35a.js} +2 -2
- flowfile/web/static/assets/{SecretsView-17df66ee.js → SecretsView-c6afc915.js} +2 -2
- flowfile/web/static/assets/{Select-0aee4c54.js → Select-fcd002b6.js} +9 -9
- flowfile/web/static/assets/{SettingsSection-cd341bb6.js → SettingsSection-5ce15962.js} +1 -1
- flowfile/web/static/assets/{SettingsSection-0784e157.js → SettingsSection-c6b1362c.js} +1 -1
- flowfile/web/static/assets/{SettingsSection-f2002a6d.js → SettingsSection-cebb91d5.js} +1 -1
- flowfile/web/static/assets/SetupView-2d12e01f.js +160 -0
- flowfile/web/static/assets/SetupView-ec26f76a.css +230 -0
- flowfile/web/static/assets/{SingleSelect-460cc0ea.js → SingleSelect-b67de4eb.js} +2 -2
- flowfile/web/static/assets/{SingleSelect.vue_vue_type_script_setup_true_lang-30741bb2.js → SingleSelect.vue_vue_type_script_setup_true_lang-eedb70eb.js} +1 -1
- flowfile/web/static/assets/{SliderInput-5d926864.js → SliderInput-fd8134ac.js} +1 -1
- flowfile/web/static/assets/Sort-4abb7fae.css +9 -0
- flowfile/web/static/assets/{Sort-3cdc971b.js → Sort-c005a573.js} +9 -9
- flowfile/web/static/assets/{TextInput-a2d0bfbd.js → TextInput-1bb31dab.js} +2 -2
- flowfile/web/static/assets/{TextInput.vue_vue_type_script_setup_true_lang-abad1ca2.js → TextInput.vue_vue_type_script_setup_true_lang-a51fe730.js} +1 -1
- flowfile/web/static/assets/{TextToRows-918945f7.js → TextToRows-4f363753.js} +9 -9
- flowfile/web/static/assets/{ToggleSwitch-f0ef5196.js → ToggleSwitch-ca0f2e5e.js} +2 -2
- flowfile/web/static/assets/{ToggleSwitch.vue_vue_type_script_setup_true_lang-5605c793.js → ToggleSwitch.vue_vue_type_script_setup_true_lang-49aa41d8.js} +1 -1
- flowfile/web/static/assets/{UnavailableFields-54d2f518.css → UnavailableFields-394a1f78.css} +13 -13
- flowfile/web/static/assets/{UnavailableFields-bdad6144.js → UnavailableFields-f6147968.js} +4 -4
- flowfile/web/static/assets/{Union-e8ab8c86.js → Union-c65f17b7.js} +6 -6
- flowfile/web/static/assets/Unique-2b705521.css +3 -0
- flowfile/web/static/assets/{Unique-8cd4f976.js → Unique-a1d96fb2.js} +12 -12
- flowfile/web/static/assets/{Unpivot-710a2948.css → Unpivot-b6ad6427.css} +6 -6
- flowfile/web/static/assets/{Unpivot-8da14095.js → Unpivot-c2657ff3.js} +11 -11
- flowfile/web/static/assets/{UnpivotValidation-6f7d89ff.js → UnpivotValidation-28e29a3b.js} +1 -1
- flowfile/web/static/assets/{VueGraphicWalker-3fb312e1.js → VueGraphicWalker-2fc3ddd4.js} +1 -1
- flowfile/web/static/assets/{api-24483f0d.js → api-df48ec50.js} +1 -1
- flowfile/web/static/assets/{api-8b81fa73.js → api-ee542cf7.js} +1 -1
- flowfile/web/static/assets/{dropDown-3d8dc5fa.css → dropDown-1d6acbd9.css} +26 -26
- flowfile/web/static/assets/{dropDown-ac0fda9d.js → dropDown-7576a76a.js} +3 -3
- flowfile/web/static/assets/{fullEditor-5497a84a.js → fullEditor-7583bef5.js} +3 -3
- flowfile/web/static/assets/{fullEditor-a0be62b3.css → fullEditor-fe9f7e18.css} +3 -3
- flowfile/web/static/assets/{genericNodeSettings-99014e1d.js → genericNodeSettings-0155288b.js} +2 -3
- flowfile/web/static/assets/{index-3ba44389.js → index-057d770d.js} +2 -2
- flowfile/web/static/assets/{index-07dda503.js → index-aeec439d.js} +1 -1
- flowfile/web/static/assets/{index-fb6493ae.js → index-ca6799de.js} +2293 -196
- flowfile/web/static/assets/{index-e6289dd0.css → index-d60c9dd4.css} +560 -10
- flowfile/web/static/assets/nodeInput-d478b9ac.js +2 -0
- flowfile/web/static/assets/{outputCsv-8f8ba42d.js → outputCsv-c492b15e.js} +3 -3
- flowfile/web/static/assets/outputCsv-cc84e09f.css +2499 -0
- flowfile/web/static/assets/{outputExcel-393f4fef.js → outputExcel-13bfa10f.js} +1 -1
- flowfile/web/static/assets/{outputParquet-07c81f65.js → outputParquet-9be1523a.js} +1 -1
- flowfile/web/static/assets/{readCsv-07f6d9ad.js → readCsv-5a49a8c9.js} +1 -1
- flowfile/web/static/assets/{readExcel-ed69bc8f.js → readExcel-27c30ad8.js} +3 -3
- flowfile/web/static/assets/{readParquet-e3ed4528.js → readParquet-446bde68.js} +1 -1
- flowfile/web/static/assets/{secrets.api-002e7d7e.js → secrets.api-34431884.js} +1 -1
- flowfile/web/static/assets/{selectDynamic-80b92899.js → selectDynamic-5754a2b1.js} +2 -3
- flowfile/web/static/assets/{vue-codemirror.esm-0965f39f.js → vue-codemirror.esm-8f46fb36.js} +1 -1
- flowfile/web/static/assets/{vue-content-loader.es-c506ad97.js → vue-content-loader.es-808fe33a.js} +1 -1
- flowfile/web/static/index.html +2 -2
- {flowfile-0.5.3.dist-info → flowfile-0.5.6.dist-info}/METADATA +2 -2
- {flowfile-0.5.3.dist-info → flowfile-0.5.6.dist-info}/RECORD +139 -134
- flowfile_core/auth/secrets.py +56 -13
- flowfile_core/fileExplorer/funcs.py +26 -4
- flowfile_core/flowfile/code_generator/__init__.py +11 -0
- flowfile_core/flowfile/code_generator/code_generator.py +347 -2
- flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +13 -1
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +12 -0
- flowfile_core/flowfile/flow_graph.py +2 -0
- flowfile_core/flowfile/flow_node/flow_node.py +52 -28
- flowfile_core/flowfile/node_designer/__init__.py +4 -0
- flowfile_core/flowfile/node_designer/ui_components.py +144 -1
- flowfile_core/main.py +2 -4
- flowfile_core/routes/public.py +43 -1
- flowfile_core/schemas/cloud_storage_schemas.py +39 -15
- flowfile_core/secret_manager/secret_manager.py +107 -6
- flowfile_frame/__init__.py +11 -0
- flowfile_frame/database/__init__.py +36 -0
- flowfile_frame/database/connection_manager.py +205 -0
- flowfile_frame/database/frame_helpers.py +249 -0
- flowfile_worker/configs.py +31 -15
- flowfile_worker/secrets.py +105 -15
- flowfile_worker/spawner.py +10 -6
- flowfile/web/static/assets/ContextMenu-26d4dd27.css +0 -26
- flowfile/web/static/assets/ContextMenu-31ee57f0.js +0 -41
- flowfile/web/static/assets/ContextMenu-69a74055.js +0 -41
- flowfile/web/static/assets/ContextMenu-8e2051c6.js +0 -41
- flowfile/web/static/assets/ContextMenu-8ec1729e.css +0 -26
- flowfile/web/static/assets/ContextMenu-9b310c60.css +0 -26
- flowfile/web/static/assets/CustomNode-59e99a86.css +0 -32
- flowfile/web/static/assets/GroupBy-be7ac0bf.css +0 -51
- flowfile/web/static/assets/PopOver-b22f049e.js +0 -939
- flowfile/web/static/assets/SQLQueryComponent-1c2f26b4.css +0 -27
- flowfile/web/static/assets/Sort-8a871341.css +0 -51
- flowfile/web/static/assets/Unique-9fb2f567.css +0 -51
- flowfile/web/static/assets/nodeInput-0eb13f1a.js +0 -2
- flowfile/web/static/assets/outputCsv-b9a072af.css +0 -2499
- {flowfile-0.5.3.dist-info → flowfile-0.5.6.dist-info}/WHEEL +0 -0
- {flowfile-0.5.3.dist-info → flowfile-0.5.6.dist-info}/entry_points.txt +0 -0
- {flowfile-0.5.3.dist-info → flowfile-0.5.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Database module for flowfile_frame.
|
|
2
|
+
|
|
3
|
+
This module provides functions for:
|
|
4
|
+
- Managing database connections (create, list, delete)
|
|
5
|
+
- Reading from databases
|
|
6
|
+
- Writing to databases
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from flowfile_frame.database.connection_manager import (
|
|
10
|
+
create_database_connection,
|
|
11
|
+
create_database_connection_if_not_exists,
|
|
12
|
+
del_database_connection,
|
|
13
|
+
get_all_available_database_connections,
|
|
14
|
+
get_database_connection_by_name,
|
|
15
|
+
)
|
|
16
|
+
from flowfile_frame.database.frame_helpers import (
|
|
17
|
+
add_read_from_database,
|
|
18
|
+
add_write_to_database,
|
|
19
|
+
read_database,
|
|
20
|
+
write_database,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
# Connection management
|
|
25
|
+
"create_database_connection",
|
|
26
|
+
"create_database_connection_if_not_exists",
|
|
27
|
+
"del_database_connection",
|
|
28
|
+
"get_all_available_database_connections",
|
|
29
|
+
"get_database_connection_by_name",
|
|
30
|
+
# FlowGraph helpers
|
|
31
|
+
"add_read_from_database",
|
|
32
|
+
"add_write_to_database",
|
|
33
|
+
# Direct read/write
|
|
34
|
+
"read_database",
|
|
35
|
+
"write_database",
|
|
36
|
+
]
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""Database connection management for flowfile_frame.
|
|
2
|
+
|
|
3
|
+
This module provides functions for managing database connections,
|
|
4
|
+
similar to how cloud_storage/secret_manager.py handles cloud storage connections.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Literal
|
|
8
|
+
|
|
9
|
+
from pydantic import SecretStr
|
|
10
|
+
|
|
11
|
+
from flowfile_core.database.connection import get_db_context
|
|
12
|
+
from flowfile_core.flowfile.database_connection_manager.db_connections import (
|
|
13
|
+
get_database_connection,
|
|
14
|
+
get_database_connection_schema,
|
|
15
|
+
store_database_connection,
|
|
16
|
+
)
|
|
17
|
+
from flowfile_core.schemas.input_schema import (
|
|
18
|
+
FullDatabaseConnection,
|
|
19
|
+
FullDatabaseConnectionInterface,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_current_user_id() -> int:
|
|
24
|
+
"""Get the current user ID for database operations.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
int: The current user ID (defaults to 1 for single-user mode).
|
|
28
|
+
"""
|
|
29
|
+
# In single-file mode, we use user_id = 1
|
|
30
|
+
return 1
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def create_database_connection(
|
|
34
|
+
connection_name: str,
|
|
35
|
+
*,
|
|
36
|
+
database_type: Literal["postgresql", "mysql", "sqlite", "mssql", "oracle"] = "postgresql",
|
|
37
|
+
host: str | None = None,
|
|
38
|
+
port: int | None = None,
|
|
39
|
+
database: str | None = None,
|
|
40
|
+
username: str | None = None,
|
|
41
|
+
password: str | SecretStr | None = None,
|
|
42
|
+
ssl_enabled: bool = False,
|
|
43
|
+
url: str | None = None,
|
|
44
|
+
) -> FullDatabaseConnection:
|
|
45
|
+
"""Create and store a new database connection.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
connection_name: Unique name for this connection.
|
|
49
|
+
database_type: Type of database (postgresql, mysql, sqlite, mssql, oracle).
|
|
50
|
+
host: Database server hostname.
|
|
51
|
+
port: Database server port.
|
|
52
|
+
database: Database name.
|
|
53
|
+
username: Database username.
|
|
54
|
+
password: Database password.
|
|
55
|
+
ssl_enabled: Whether to use SSL for the connection.
|
|
56
|
+
url: Full database URL (overrides other connection parameters).
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
FullDatabaseConnection: The created connection object.
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
ValueError: If a connection with this name already exists.
|
|
63
|
+
"""
|
|
64
|
+
user_id = get_current_user_id()
|
|
65
|
+
|
|
66
|
+
# Convert password to SecretStr if it's a plain string
|
|
67
|
+
if isinstance(password, str):
|
|
68
|
+
password = SecretStr(password)
|
|
69
|
+
|
|
70
|
+
connection = FullDatabaseConnection(
|
|
71
|
+
connection_name=connection_name,
|
|
72
|
+
database_type=database_type,
|
|
73
|
+
host=host,
|
|
74
|
+
port=port,
|
|
75
|
+
database=database,
|
|
76
|
+
username=username,
|
|
77
|
+
password=password,
|
|
78
|
+
ssl_enabled=ssl_enabled,
|
|
79
|
+
url=url,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
with get_db_context() as db:
|
|
83
|
+
store_database_connection(db, connection, user_id)
|
|
84
|
+
|
|
85
|
+
return connection
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def create_database_connection_if_not_exists(
|
|
89
|
+
connection_name: str,
|
|
90
|
+
*,
|
|
91
|
+
database_type: Literal["postgresql", "mysql", "sqlite", "mssql", "oracle"] = "postgresql",
|
|
92
|
+
host: str | None = None,
|
|
93
|
+
port: int | None = None,
|
|
94
|
+
database: str | None = None,
|
|
95
|
+
username: str | None = None,
|
|
96
|
+
password: str | SecretStr | None = None,
|
|
97
|
+
ssl_enabled: bool = False,
|
|
98
|
+
url: str | None = None,
|
|
99
|
+
) -> FullDatabaseConnection:
|
|
100
|
+
"""Create a database connection if it doesn't already exist.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
connection_name: Unique name for this connection.
|
|
104
|
+
database_type: Type of database (postgresql, mysql, sqlite, mssql, oracle).
|
|
105
|
+
host: Database server hostname.
|
|
106
|
+
port: Database server port.
|
|
107
|
+
database: Database name.
|
|
108
|
+
username: Database username.
|
|
109
|
+
password: Database password.
|
|
110
|
+
ssl_enabled: Whether to use SSL for the connection.
|
|
111
|
+
url: Full database URL (overrides other connection parameters).
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
FullDatabaseConnection: The existing or newly created connection.
|
|
115
|
+
"""
|
|
116
|
+
user_id = get_current_user_id()
|
|
117
|
+
|
|
118
|
+
# Check if connection already exists
|
|
119
|
+
existing = get_database_connection_by_name(connection_name)
|
|
120
|
+
if existing:
|
|
121
|
+
return existing
|
|
122
|
+
|
|
123
|
+
return create_database_connection(
|
|
124
|
+
connection_name,
|
|
125
|
+
database_type=database_type,
|
|
126
|
+
host=host,
|
|
127
|
+
port=port,
|
|
128
|
+
database=database,
|
|
129
|
+
username=username,
|
|
130
|
+
password=password,
|
|
131
|
+
ssl_enabled=ssl_enabled,
|
|
132
|
+
url=url,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def get_database_connection_by_name(connection_name: str) -> FullDatabaseConnection | None:
|
|
137
|
+
"""Get a database connection by its name.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
connection_name: The name of the connection to retrieve.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
FullDatabaseConnection if found, None otherwise.
|
|
144
|
+
"""
|
|
145
|
+
user_id = get_current_user_id()
|
|
146
|
+
with get_db_context() as db:
|
|
147
|
+
return get_database_connection_schema(db, connection_name, user_id)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def get_all_available_database_connections() -> list[FullDatabaseConnectionInterface]:
|
|
151
|
+
"""Get all available database connections for the current user.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
List of database connection interfaces (without passwords).
|
|
155
|
+
"""
|
|
156
|
+
from flowfile_core.database.models import DatabaseConnection as DBConnectionModel
|
|
157
|
+
|
|
158
|
+
user_id = get_current_user_id()
|
|
159
|
+
with get_db_context() as db:
|
|
160
|
+
connections = (
|
|
161
|
+
db.query(DBConnectionModel)
|
|
162
|
+
.filter(DBConnectionModel.user_id == user_id)
|
|
163
|
+
.all()
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
return [
|
|
167
|
+
FullDatabaseConnectionInterface(
|
|
168
|
+
connection_name=conn.connection_name,
|
|
169
|
+
database_type=conn.database_type,
|
|
170
|
+
username=conn.username,
|
|
171
|
+
host=conn.host,
|
|
172
|
+
port=conn.port,
|
|
173
|
+
database=conn.database,
|
|
174
|
+
ssl_enabled=conn.ssl_enabled,
|
|
175
|
+
)
|
|
176
|
+
for conn in connections
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def del_database_connection(connection_name: str) -> bool:
|
|
181
|
+
"""Delete a database connection by its name.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
connection_name: The name of the connection to delete.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
True if the connection was deleted, False if it didn't exist.
|
|
188
|
+
"""
|
|
189
|
+
from flowfile_core.database.models import DatabaseConnection as DBConnectionModel
|
|
190
|
+
from flowfile_core.database.models import Secret
|
|
191
|
+
|
|
192
|
+
user_id = get_current_user_id()
|
|
193
|
+
with get_db_context() as db:
|
|
194
|
+
connection = get_database_connection(db, connection_name, user_id)
|
|
195
|
+
if connection:
|
|
196
|
+
# Delete the associated password secret
|
|
197
|
+
if connection.password_id:
|
|
198
|
+
secret = db.query(Secret).filter(Secret.id == connection.password_id).first()
|
|
199
|
+
if secret:
|
|
200
|
+
db.delete(secret)
|
|
201
|
+
|
|
202
|
+
db.delete(connection)
|
|
203
|
+
db.commit()
|
|
204
|
+
return True
|
|
205
|
+
return False
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""Database helper functions for FlowFrame operations.
|
|
2
|
+
|
|
3
|
+
This module provides functions for reading from and writing to databases,
|
|
4
|
+
similar to how cloud_storage/frame_helpers.py handles cloud storage operations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Literal
|
|
8
|
+
|
|
9
|
+
import polars as pl
|
|
10
|
+
|
|
11
|
+
from flowfile_core.flowfile.flow_graph import FlowGraph
|
|
12
|
+
from flowfile_core.schemas import input_schema
|
|
13
|
+
from flowfile_frame.database.connection_manager import get_current_user_id
|
|
14
|
+
from flowfile_frame.utils import generate_node_id
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def add_read_from_database(
|
|
18
|
+
flow_graph: FlowGraph,
|
|
19
|
+
*,
|
|
20
|
+
connection_name: str,
|
|
21
|
+
table_name: str | None = None,
|
|
22
|
+
schema_name: str | None = None,
|
|
23
|
+
query: str | None = None,
|
|
24
|
+
description: str | None = None,
|
|
25
|
+
) -> int:
|
|
26
|
+
"""Add a database reader node to the flow graph.
|
|
27
|
+
|
|
28
|
+
Either table_name or query must be provided. If both are provided,
|
|
29
|
+
query takes precedence.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
flow_graph: The flow graph to add the node to.
|
|
33
|
+
connection_name: Name of the stored database connection to use.
|
|
34
|
+
table_name: Name of the table to read from.
|
|
35
|
+
schema_name: Database schema name (e.g., 'public' for PostgreSQL).
|
|
36
|
+
query: SQL query to execute instead of reading a table.
|
|
37
|
+
description: Optional description for the node.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
int: The node ID of the created database reader node.
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
ValueError: If neither table_name nor query is provided.
|
|
44
|
+
"""
|
|
45
|
+
if table_name is None and query is None:
|
|
46
|
+
raise ValueError("Either 'table_name' or 'query' must be provided")
|
|
47
|
+
|
|
48
|
+
node_id = generate_node_id()
|
|
49
|
+
flow_id = flow_graph.flow_id
|
|
50
|
+
|
|
51
|
+
# Determine query mode
|
|
52
|
+
query_mode: Literal["table", "query"] = "query" if query else "table"
|
|
53
|
+
|
|
54
|
+
settings = input_schema.NodeDatabaseReader(
|
|
55
|
+
flow_id=flow_id,
|
|
56
|
+
node_id=node_id,
|
|
57
|
+
user_id=get_current_user_id(),
|
|
58
|
+
description=description,
|
|
59
|
+
database_settings=input_schema.DatabaseSettings(
|
|
60
|
+
connection_mode="reference",
|
|
61
|
+
database_connection_name=connection_name,
|
|
62
|
+
query_mode=query_mode,
|
|
63
|
+
table_name=table_name,
|
|
64
|
+
schema_name=schema_name,
|
|
65
|
+
query=query,
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
flow_graph.add_database_reader(settings)
|
|
70
|
+
return node_id
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def add_write_to_database(
|
|
74
|
+
flow_graph: FlowGraph,
|
|
75
|
+
depends_on_node_id: int,
|
|
76
|
+
*,
|
|
77
|
+
connection_name: str,
|
|
78
|
+
table_name: str,
|
|
79
|
+
schema_name: str | None = None,
|
|
80
|
+
if_exists: Literal["append", "replace", "fail"] = "append",
|
|
81
|
+
description: str | None = None,
|
|
82
|
+
) -> int:
|
|
83
|
+
"""Add a database writer node to the flow graph.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
flow_graph: The flow graph to add the node to.
|
|
87
|
+
depends_on_node_id: The node ID that this writer depends on.
|
|
88
|
+
connection_name: Name of the stored database connection to use.
|
|
89
|
+
table_name: Name of the table to write to.
|
|
90
|
+
schema_name: Database schema name (e.g., 'public' for PostgreSQL).
|
|
91
|
+
if_exists: What to do if the table already exists:
|
|
92
|
+
- 'append': Add rows to existing table
|
|
93
|
+
- 'replace': Drop and recreate table
|
|
94
|
+
- 'fail': Raise an error
|
|
95
|
+
description: Optional description for the node.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
int: The node ID of the created database writer node.
|
|
99
|
+
"""
|
|
100
|
+
node_id = generate_node_id()
|
|
101
|
+
flow_id = flow_graph.flow_id
|
|
102
|
+
|
|
103
|
+
settings = input_schema.NodeDatabaseWriter(
|
|
104
|
+
flow_id=flow_id,
|
|
105
|
+
node_id=node_id,
|
|
106
|
+
user_id=get_current_user_id(),
|
|
107
|
+
depending_on_id=depends_on_node_id,
|
|
108
|
+
description=description,
|
|
109
|
+
database_write_settings=input_schema.DatabaseWriteSettings(
|
|
110
|
+
connection_mode="reference",
|
|
111
|
+
database_connection_name=connection_name,
|
|
112
|
+
table_name=table_name,
|
|
113
|
+
schema_name=schema_name,
|
|
114
|
+
if_exists=if_exists,
|
|
115
|
+
),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
flow_graph.add_database_writer(settings)
|
|
119
|
+
return node_id
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def read_database(
|
|
123
|
+
connection_name: str,
|
|
124
|
+
*,
|
|
125
|
+
table_name: str | None = None,
|
|
126
|
+
schema_name: str | None = None,
|
|
127
|
+
query: str | None = None,
|
|
128
|
+
) -> pl.LazyFrame:
|
|
129
|
+
"""Read data from a database using a stored connection.
|
|
130
|
+
|
|
131
|
+
This is a convenience function for reading data directly without
|
|
132
|
+
needing to create a FlowGraph.
|
|
133
|
+
|
|
134
|
+
Either table_name or query must be provided. If both are provided,
|
|
135
|
+
query takes precedence.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
connection_name: Name of the stored database connection to use.
|
|
139
|
+
table_name: Name of the table to read from.
|
|
140
|
+
schema_name: Database schema name (e.g., 'public' for PostgreSQL).
|
|
141
|
+
query: SQL query to execute instead of reading a table.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
pl.LazyFrame: The data read from the database.
|
|
145
|
+
|
|
146
|
+
Raises:
|
|
147
|
+
ValueError: If neither table_name nor query is provided.
|
|
148
|
+
ValueError: If the connection is not found.
|
|
149
|
+
"""
|
|
150
|
+
from flowfile_core.flowfile.database_connection_manager.db_connections import get_local_database_connection
|
|
151
|
+
from flowfile_core.flowfile.sources.external_sources.sql_source.sql_source import SqlSource
|
|
152
|
+
from flowfile_core.flowfile.sources.external_sources.sql_source.utils import construct_sql_uri
|
|
153
|
+
from flowfile_core.secret_manager.secret_manager import decrypt_secret
|
|
154
|
+
|
|
155
|
+
if table_name is None and query is None:
|
|
156
|
+
raise ValueError("Either 'table_name' or 'query' must be provided")
|
|
157
|
+
|
|
158
|
+
user_id = get_current_user_id()
|
|
159
|
+
connection = get_local_database_connection(connection_name, user_id)
|
|
160
|
+
|
|
161
|
+
if connection is None:
|
|
162
|
+
raise ValueError(f"Database connection '{connection_name}' not found")
|
|
163
|
+
|
|
164
|
+
# Construct the connection string
|
|
165
|
+
connection_string = construct_sql_uri(
|
|
166
|
+
database_type=connection.database_type,
|
|
167
|
+
host=connection.host,
|
|
168
|
+
port=connection.port,
|
|
169
|
+
database=connection.database,
|
|
170
|
+
username=connection.username,
|
|
171
|
+
password=decrypt_secret(connection.password.get_secret_value()),
|
|
172
|
+
url=connection.url,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Create SQL source and read data
|
|
176
|
+
sql_source = SqlSource(
|
|
177
|
+
connection_string=connection_string,
|
|
178
|
+
query=query,
|
|
179
|
+
table_name=table_name,
|
|
180
|
+
schema_name=schema_name,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
return sql_source.get_data()
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def write_database(
|
|
187
|
+
df: pl.DataFrame | pl.LazyFrame,
|
|
188
|
+
connection_name: str,
|
|
189
|
+
table_name: str,
|
|
190
|
+
*,
|
|
191
|
+
schema_name: str | None = None,
|
|
192
|
+
if_exists: Literal["append", "replace", "fail"] = "append",
|
|
193
|
+
) -> None:
|
|
194
|
+
"""Write data to a database using a stored connection.
|
|
195
|
+
|
|
196
|
+
This is a convenience function for writing data directly without
|
|
197
|
+
needing to create a FlowGraph.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
df: The DataFrame or LazyFrame to write.
|
|
201
|
+
connection_name: Name of the stored database connection to use.
|
|
202
|
+
table_name: Name of the table to write to.
|
|
203
|
+
schema_name: Database schema name (e.g., 'public' for PostgreSQL).
|
|
204
|
+
if_exists: What to do if the table already exists:
|
|
205
|
+
- 'append': Add rows to existing table
|
|
206
|
+
- 'replace': Drop and recreate table
|
|
207
|
+
- 'fail': Raise an error
|
|
208
|
+
|
|
209
|
+
Raises:
|
|
210
|
+
ValueError: If the connection is not found.
|
|
211
|
+
"""
|
|
212
|
+
from sqlalchemy import create_engine
|
|
213
|
+
|
|
214
|
+
from flowfile_core.flowfile.database_connection_manager.db_connections import get_local_database_connection
|
|
215
|
+
from flowfile_core.flowfile.sources.external_sources.sql_source.utils import construct_sql_uri
|
|
216
|
+
from flowfile_core.secret_manager.secret_manager import decrypt_secret
|
|
217
|
+
|
|
218
|
+
user_id = get_current_user_id()
|
|
219
|
+
connection = get_local_database_connection(connection_name, user_id)
|
|
220
|
+
|
|
221
|
+
if connection is None:
|
|
222
|
+
raise ValueError(f"Database connection '{connection_name}' not found")
|
|
223
|
+
|
|
224
|
+
# Collect if LazyFrame
|
|
225
|
+
if isinstance(df, pl.LazyFrame):
|
|
226
|
+
df = df.collect()
|
|
227
|
+
|
|
228
|
+
# Construct the connection string
|
|
229
|
+
connection_string = construct_sql_uri(
|
|
230
|
+
database_type=connection.database_type,
|
|
231
|
+
host=connection.host,
|
|
232
|
+
port=connection.port,
|
|
233
|
+
database=connection.database,
|
|
234
|
+
username=connection.username,
|
|
235
|
+
password=decrypt_secret(connection.password.get_secret_value()),
|
|
236
|
+
url=connection.url,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
# Write to database using pandas (polars doesn't have direct SQL write support)
|
|
240
|
+
engine = create_engine(connection_string)
|
|
241
|
+
pandas_df = df.to_pandas()
|
|
242
|
+
|
|
243
|
+
pandas_df.to_sql(
|
|
244
|
+
name=table_name,
|
|
245
|
+
con=engine,
|
|
246
|
+
schema=schema_name,
|
|
247
|
+
if_exists=if_exists,
|
|
248
|
+
index=False,
|
|
249
|
+
)
|
flowfile_worker/configs.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import logging
|
|
5
|
+
import multiprocessing
|
|
5
6
|
import os
|
|
6
7
|
import platform
|
|
7
8
|
|
|
@@ -15,8 +16,9 @@ logger.setLevel(logging.INFO)
|
|
|
15
16
|
# Constants for worker and core configuration
|
|
16
17
|
DEFAULT_SERVICE_HOST = "0.0.0.0" if platform.system() != "Windows" else "127.0.0.1"
|
|
17
18
|
DEFAULT_SERVICE_PORT = 63579
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
# Check environment variable for core host (used in Docker mode)
|
|
20
|
+
DEFAULT_CORE_HOST = os.environ.get("CORE_HOST", "0.0.0.0" if platform.system() != "Windows" else "127.0.0.1")
|
|
21
|
+
DEFAULT_CORE_PORT = int(os.environ.get("CORE_PORT", 63578))
|
|
20
22
|
TEST_MODE = True if "TEST_MODE" in os.environ else False
|
|
21
23
|
|
|
22
24
|
|
|
@@ -69,19 +71,33 @@ def get_core_url(host, port):
|
|
|
69
71
|
return f"http://{host}:{port}"
|
|
70
72
|
|
|
71
73
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
def _is_main_process():
|
|
75
|
+
"""Check if we're running in the main process (not a spawned child)"""
|
|
76
|
+
return multiprocessing.current_process().name == "MainProcess"
|
|
74
77
|
|
|
75
|
-
# These variables will already use defaults from argparse if not provided
|
|
76
|
-
SERVICE_HOST = args.host
|
|
77
|
-
SERVICE_PORT = args.port
|
|
78
|
-
CORE_HOST = args.core_host
|
|
79
|
-
CORE_PORT = args.core_port
|
|
80
78
|
|
|
81
|
-
#
|
|
82
|
-
|
|
79
|
+
# Only parse args and log in the main process
|
|
80
|
+
# Spawned child processes will use environment variables or defaults
|
|
81
|
+
if _is_main_process():
|
|
82
|
+
# Parse arguments - defaults are already set in the argument parser
|
|
83
|
+
args = parse_args()
|
|
84
|
+
|
|
85
|
+
# These variables will already use defaults from argparse if not provided
|
|
86
|
+
SERVICE_HOST = args.host
|
|
87
|
+
SERVICE_PORT = args.port
|
|
88
|
+
CORE_HOST = args.core_host
|
|
89
|
+
CORE_PORT = args.core_port
|
|
83
90
|
|
|
84
|
-
logger.info(f"ConnectorX version: {__version__}")
|
|
85
|
-
# Log configuration
|
|
86
|
-
logger.info(f"Worker configured at {SERVICE_HOST}:{SERVICE_PORT}")
|
|
87
|
-
logger.info(f"Core service configured at {
|
|
91
|
+
logger.info(f"ConnectorX version: {__version__}")
|
|
92
|
+
# Log configuration
|
|
93
|
+
logger.info(f"Worker configured at {SERVICE_HOST}:{SERVICE_PORT}")
|
|
94
|
+
logger.info(f"Core service configured at {get_core_url(CORE_HOST, CORE_PORT)}")
|
|
95
|
+
else:
|
|
96
|
+
# In spawned processes, use defaults from environment variables
|
|
97
|
+
SERVICE_HOST = DEFAULT_SERVICE_HOST
|
|
98
|
+
SERVICE_PORT = DEFAULT_SERVICE_PORT
|
|
99
|
+
CORE_HOST = DEFAULT_CORE_HOST
|
|
100
|
+
CORE_PORT = DEFAULT_CORE_PORT
|
|
101
|
+
|
|
102
|
+
# Generate the core URI (used by both main and spawned processes)
|
|
103
|
+
FLOWFILE_CORE_URI = get_core_url(CORE_HOST, CORE_PORT)
|