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.
Files changed (153) hide show
  1. flowfile/__init__.py +16 -0
  2. flowfile/__main__.py +94 -1
  3. flowfile/web/static/assets/{AdminView-49392a9a.js → AdminView-c2c7942b.js} +1 -1
  4. flowfile/web/static/assets/{CloudConnectionView-f13f202b.js → CloudConnectionView-7a3042c6.js} +4 -4
  5. flowfile/web/static/assets/{CloudConnectionView-36bcd6df.css → CloudConnectionView-cf85f943.css} +17 -17
  6. flowfile/web/static/assets/{CloudStorageReader-0023d4a5.js → CloudStorageReader-709c4037.js} +8 -8
  7. flowfile/web/static/assets/{CloudStorageWriter-8e781e11.js → CloudStorageWriter-604c51a8.js} +8 -8
  8. flowfile/web/static/assets/ColumnActionInput-c44b7aee.css +159 -0
  9. flowfile/web/static/assets/ColumnActionInput-d63d6746.js +330 -0
  10. flowfile/web/static/assets/{ColumnSelector-8ad68ea9.js → ColumnSelector-0c8cd1cd.js} +1 -1
  11. flowfile/web/static/assets/ContextMenu-366bf1b4.js +9 -0
  12. flowfile/web/static/assets/ContextMenu-85cf5b44.js +9 -0
  13. flowfile/web/static/assets/ContextMenu-9d28ae6d.js +9 -0
  14. flowfile/web/static/assets/ContextMenu.vue_vue_type_script_setup_true_lang-774c517c.js +59 -0
  15. flowfile/web/static/assets/{CrossJoin-03df6938.js → CrossJoin-38e5b99a.js} +9 -9
  16. flowfile/web/static/assets/{CustomNode-8479239b.js → CustomNode-76e8f3f5.js} +27 -20
  17. flowfile/web/static/assets/CustomNode-edb9b939.css +42 -0
  18. flowfile/web/static/assets/{DatabaseConnectionSettings-869e3efd.js → DatabaseConnectionSettings-38155669.js} +4 -4
  19. flowfile/web/static/assets/{DatabaseConnectionSettings-e91df89a.css → DatabaseConnectionSettings-c20a1e16.css} +22 -20
  20. flowfile/web/static/assets/{DatabaseReader-c58b9552.js → DatabaseReader-2e549c8f.js} +13 -13
  21. flowfile/web/static/assets/{DatabaseReader-36898a00.css → DatabaseReader-5bf8c75b.css} +39 -44
  22. flowfile/web/static/assets/{DatabaseView-d26a9140.js → DatabaseView-dc877c29.js} +2 -2
  23. flowfile/web/static/assets/{DatabaseWriter-217a99f1.css → DatabaseWriter-bdcf2c8b.css} +27 -25
  24. flowfile/web/static/assets/{DatabaseWriter-4d05ddc7.js → DatabaseWriter-ffb91864.js} +12 -12
  25. flowfile/web/static/assets/{DesignerView-a6d0ee84.css → DesignerView-71d4e9a1.css} +429 -376
  26. flowfile/web/static/assets/{DesignerView-e6f5c0e8.js → DesignerView-a4466dab.js} +338 -183
  27. flowfile/web/static/assets/{DocumentationView-2e78ef1b.js → DocumentationView-979afc84.js} +3 -3
  28. flowfile/web/static/assets/{DocumentationView-fd46c656.css → DocumentationView-9ea6e871.css} +9 -9
  29. flowfile/web/static/assets/{ExploreData-7b54caca.js → ExploreData-e4b92aaf.js} +7 -7
  30. flowfile/web/static/assets/{ExternalSource-47ab05a3.css → ExternalSource-7ac7373f.css} +17 -17
  31. flowfile/web/static/assets/{ExternalSource-3fa399b2.js → ExternalSource-d08e7227.js} +9 -9
  32. flowfile/web/static/assets/{Filter-8cbbdbf3.js → Filter-7add806d.js} +9 -9
  33. flowfile/web/static/assets/{Formula-aac42b1e.js → Formula-36ab24d2.js} +9 -9
  34. flowfile/web/static/assets/{FuzzyMatch-cd9bbfca.js → FuzzyMatch-cc01bb04.js} +10 -10
  35. flowfile/web/static/assets/{GraphSolver-c24dec17.css → GraphSolver-4b4d7db9.css} +4 -4
  36. flowfile/web/static/assets/{GraphSolver-c7e6780e.js → GraphSolver-4fb98f3b.js} +11 -11
  37. flowfile/web/static/assets/GroupBy-5792782d.css +9 -0
  38. flowfile/web/static/assets/{GroupBy-93c5d22b.js → GroupBy-b3c8f429.js} +9 -9
  39. flowfile/web/static/assets/{Join-a19b2de2.js → Join-096b7b26.js} +10 -10
  40. flowfile/web/static/assets/{LoginView-0df4ed0a.js → LoginView-c33a246a.js} +1 -1
  41. flowfile/web/static/assets/{ManualInput-3702e677.css → ManualInput-39111f19.css} +48 -48
  42. flowfile/web/static/assets/{ManualInput-8d3374b2.js → ManualInput-7307e9b1.js} +55 -13
  43. flowfile/web/static/assets/{MultiSelect-ad1b6243.js → MultiSelect-14822c48.js} +2 -2
  44. 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
  45. flowfile/web/static/assets/{NodeDesigner-40b647c9.js → NodeDesigner-5036c392.js} +171 -69
  46. flowfile/web/static/assets/{NodeDesigner-5f53be3f.css → NodeDesigner-94cd4dd3.css} +190 -190
  47. flowfile/web/static/assets/{NumericInput-7100234c.js → NumericInput-15cf3b72.js} +2 -2
  48. 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
  49. flowfile/web/static/assets/{Output-f5efd2aa.js → Output-1f8ed42c.js} +13 -12
  50. flowfile/web/static/assets/{Output-35e97000.css → Output-692dd25d.css} +10 -10
  51. flowfile/web/static/assets/{Pivot-d981d23c.js → Pivot-0e153f4e.js} +10 -10
  52. flowfile/web/static/assets/{PivotValidation-63de1f73.js → PivotValidation-5a4f7c79.js} +1 -1
  53. flowfile/web/static/assets/{PivotValidation-39386e95.js → PivotValidation-81ec2a33.js} +1 -1
  54. flowfile/web/static/assets/{PolarsCode-f9d69217.js → PolarsCode-a39f15ac.js} +7 -7
  55. flowfile/web/static/assets/PopOver-ddcfe4f6.js +138 -0
  56. flowfile/web/static/assets/{Read-aec2e377.js → Read-39b63932.js} +15 -14
  57. flowfile/web/static/assets/{Read-36e7bd51.css → Read-90f366bc.css} +13 -13
  58. flowfile/web/static/assets/{RecordCount-78ed6845.js → RecordCount-e9048ccd.js} +6 -6
  59. flowfile/web/static/assets/{RecordId-2156e890.js → RecordId-ad02521d.js} +9 -9
  60. flowfile/web/static/assets/{SQLQueryComponent-48c72f5b.js → SQLQueryComponent-2eeecf0b.js} +3 -3
  61. flowfile/web/static/assets/SQLQueryComponent-edb90b98.css +29 -0
  62. flowfile/web/static/assets/{Sample-1352ca74.js → Sample-9a68c23d.js} +6 -6
  63. flowfile/web/static/assets/{SecretSelector-22b5ff89.js → SecretSelector-2429f35a.js} +2 -2
  64. flowfile/web/static/assets/{SecretsView-17df66ee.js → SecretsView-c6afc915.js} +2 -2
  65. flowfile/web/static/assets/{Select-0aee4c54.js → Select-fcd002b6.js} +9 -9
  66. flowfile/web/static/assets/{SettingsSection-cd341bb6.js → SettingsSection-5ce15962.js} +1 -1
  67. flowfile/web/static/assets/{SettingsSection-0784e157.js → SettingsSection-c6b1362c.js} +1 -1
  68. flowfile/web/static/assets/{SettingsSection-f2002a6d.js → SettingsSection-cebb91d5.js} +1 -1
  69. flowfile/web/static/assets/SetupView-2d12e01f.js +160 -0
  70. flowfile/web/static/assets/SetupView-ec26f76a.css +230 -0
  71. flowfile/web/static/assets/{SingleSelect-460cc0ea.js → SingleSelect-b67de4eb.js} +2 -2
  72. 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
  73. flowfile/web/static/assets/{SliderInput-5d926864.js → SliderInput-fd8134ac.js} +1 -1
  74. flowfile/web/static/assets/Sort-4abb7fae.css +9 -0
  75. flowfile/web/static/assets/{Sort-3cdc971b.js → Sort-c005a573.js} +9 -9
  76. flowfile/web/static/assets/{TextInput-a2d0bfbd.js → TextInput-1bb31dab.js} +2 -2
  77. 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
  78. flowfile/web/static/assets/{TextToRows-918945f7.js → TextToRows-4f363753.js} +9 -9
  79. flowfile/web/static/assets/{ToggleSwitch-f0ef5196.js → ToggleSwitch-ca0f2e5e.js} +2 -2
  80. 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
  81. flowfile/web/static/assets/{UnavailableFields-54d2f518.css → UnavailableFields-394a1f78.css} +13 -13
  82. flowfile/web/static/assets/{UnavailableFields-bdad6144.js → UnavailableFields-f6147968.js} +4 -4
  83. flowfile/web/static/assets/{Union-e8ab8c86.js → Union-c65f17b7.js} +6 -6
  84. flowfile/web/static/assets/Unique-2b705521.css +3 -0
  85. flowfile/web/static/assets/{Unique-8cd4f976.js → Unique-a1d96fb2.js} +12 -12
  86. flowfile/web/static/assets/{Unpivot-710a2948.css → Unpivot-b6ad6427.css} +6 -6
  87. flowfile/web/static/assets/{Unpivot-8da14095.js → Unpivot-c2657ff3.js} +11 -11
  88. flowfile/web/static/assets/{UnpivotValidation-6f7d89ff.js → UnpivotValidation-28e29a3b.js} +1 -1
  89. flowfile/web/static/assets/{VueGraphicWalker-3fb312e1.js → VueGraphicWalker-2fc3ddd4.js} +1 -1
  90. flowfile/web/static/assets/{api-24483f0d.js → api-df48ec50.js} +1 -1
  91. flowfile/web/static/assets/{api-8b81fa73.js → api-ee542cf7.js} +1 -1
  92. flowfile/web/static/assets/{dropDown-3d8dc5fa.css → dropDown-1d6acbd9.css} +26 -26
  93. flowfile/web/static/assets/{dropDown-ac0fda9d.js → dropDown-7576a76a.js} +3 -3
  94. flowfile/web/static/assets/{fullEditor-5497a84a.js → fullEditor-7583bef5.js} +3 -3
  95. flowfile/web/static/assets/{fullEditor-a0be62b3.css → fullEditor-fe9f7e18.css} +3 -3
  96. flowfile/web/static/assets/{genericNodeSettings-99014e1d.js → genericNodeSettings-0155288b.js} +2 -3
  97. flowfile/web/static/assets/{index-3ba44389.js → index-057d770d.js} +2 -2
  98. flowfile/web/static/assets/{index-07dda503.js → index-aeec439d.js} +1 -1
  99. flowfile/web/static/assets/{index-fb6493ae.js → index-ca6799de.js} +2293 -196
  100. flowfile/web/static/assets/{index-e6289dd0.css → index-d60c9dd4.css} +560 -10
  101. flowfile/web/static/assets/nodeInput-d478b9ac.js +2 -0
  102. flowfile/web/static/assets/{outputCsv-8f8ba42d.js → outputCsv-c492b15e.js} +3 -3
  103. flowfile/web/static/assets/outputCsv-cc84e09f.css +2499 -0
  104. flowfile/web/static/assets/{outputExcel-393f4fef.js → outputExcel-13bfa10f.js} +1 -1
  105. flowfile/web/static/assets/{outputParquet-07c81f65.js → outputParquet-9be1523a.js} +1 -1
  106. flowfile/web/static/assets/{readCsv-07f6d9ad.js → readCsv-5a49a8c9.js} +1 -1
  107. flowfile/web/static/assets/{readExcel-ed69bc8f.js → readExcel-27c30ad8.js} +3 -3
  108. flowfile/web/static/assets/{readParquet-e3ed4528.js → readParquet-446bde68.js} +1 -1
  109. flowfile/web/static/assets/{secrets.api-002e7d7e.js → secrets.api-34431884.js} +1 -1
  110. flowfile/web/static/assets/{selectDynamic-80b92899.js → selectDynamic-5754a2b1.js} +2 -3
  111. flowfile/web/static/assets/{vue-codemirror.esm-0965f39f.js → vue-codemirror.esm-8f46fb36.js} +1 -1
  112. flowfile/web/static/assets/{vue-content-loader.es-c506ad97.js → vue-content-loader.es-808fe33a.js} +1 -1
  113. flowfile/web/static/index.html +2 -2
  114. {flowfile-0.5.3.dist-info → flowfile-0.5.6.dist-info}/METADATA +2 -2
  115. {flowfile-0.5.3.dist-info → flowfile-0.5.6.dist-info}/RECORD +139 -134
  116. flowfile_core/auth/secrets.py +56 -13
  117. flowfile_core/fileExplorer/funcs.py +26 -4
  118. flowfile_core/flowfile/code_generator/__init__.py +11 -0
  119. flowfile_core/flowfile/code_generator/code_generator.py +347 -2
  120. flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +13 -1
  121. flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +12 -0
  122. flowfile_core/flowfile/flow_graph.py +2 -0
  123. flowfile_core/flowfile/flow_node/flow_node.py +52 -28
  124. flowfile_core/flowfile/node_designer/__init__.py +4 -0
  125. flowfile_core/flowfile/node_designer/ui_components.py +144 -1
  126. flowfile_core/main.py +2 -4
  127. flowfile_core/routes/public.py +43 -1
  128. flowfile_core/schemas/cloud_storage_schemas.py +39 -15
  129. flowfile_core/secret_manager/secret_manager.py +107 -6
  130. flowfile_frame/__init__.py +11 -0
  131. flowfile_frame/database/__init__.py +36 -0
  132. flowfile_frame/database/connection_manager.py +205 -0
  133. flowfile_frame/database/frame_helpers.py +249 -0
  134. flowfile_worker/configs.py +31 -15
  135. flowfile_worker/secrets.py +105 -15
  136. flowfile_worker/spawner.py +10 -6
  137. flowfile/web/static/assets/ContextMenu-26d4dd27.css +0 -26
  138. flowfile/web/static/assets/ContextMenu-31ee57f0.js +0 -41
  139. flowfile/web/static/assets/ContextMenu-69a74055.js +0 -41
  140. flowfile/web/static/assets/ContextMenu-8e2051c6.js +0 -41
  141. flowfile/web/static/assets/ContextMenu-8ec1729e.css +0 -26
  142. flowfile/web/static/assets/ContextMenu-9b310c60.css +0 -26
  143. flowfile/web/static/assets/CustomNode-59e99a86.css +0 -32
  144. flowfile/web/static/assets/GroupBy-be7ac0bf.css +0 -51
  145. flowfile/web/static/assets/PopOver-b22f049e.js +0 -939
  146. flowfile/web/static/assets/SQLQueryComponent-1c2f26b4.css +0 -27
  147. flowfile/web/static/assets/Sort-8a871341.css +0 -51
  148. flowfile/web/static/assets/Unique-9fb2f567.css +0 -51
  149. flowfile/web/static/assets/nodeInput-0eb13f1a.js +0 -2
  150. flowfile/web/static/assets/outputCsv-b9a072af.css +0 -2499
  151. {flowfile-0.5.3.dist-info → flowfile-0.5.6.dist-info}/WHEEL +0 -0
  152. {flowfile-0.5.3.dist-info → flowfile-0.5.6.dist-info}/entry_points.txt +0 -0
  153. {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
+ )
@@ -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
- DEFAULT_CORE_HOST = "0.0.0.0" if platform.system() != "Windows" else "127.0.0.1"
19
- DEFAULT_CORE_PORT = 63578
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
- # Parse arguments - defaults are already set in the argument parser
73
- args = parse_args()
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
- # Generate the core URI
82
- FLOWFILE_CORE_URI = get_core_url(CORE_HOST, CORE_PORT)
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 {FLOWFILE_CORE_URI}")
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)