Flowfile 0.3.9__py3-none-any.whl → 0.5.1__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 (201) hide show
  1. flowfile/__init__.py +8 -1
  2. flowfile/api.py +1 -3
  3. flowfile/web/static/assets/{CloudConnectionManager-c97c25f8.js → CloudConnectionManager-0dfba9f2.js} +2 -2
  4. flowfile/web/static/assets/{CloudStorageReader-f1ff509e.js → CloudStorageReader-d5b1b6c9.js} +11 -78
  5. flowfile/web/static/assets/{CloudStorageWriter-034f8b78.js → CloudStorageWriter-00d87aad.js} +12 -79
  6. flowfile/web/static/assets/{CloudStorageWriter-49c9a4b2.css → CloudStorageWriter-b0ee067f.css} +24 -24
  7. flowfile/web/static/assets/ColumnSelector-4685e75d.js +83 -0
  8. flowfile/web/static/assets/ColumnSelector-47996a16.css +10 -0
  9. flowfile/web/static/assets/ContextMenu-23e909da.js +41 -0
  10. flowfile/web/static/assets/{SettingsSection-9c836ecc.css → ContextMenu-4c74eef1.css} +0 -21
  11. flowfile/web/static/assets/ContextMenu-63cfa99b.css +26 -0
  12. flowfile/web/static/assets/ContextMenu-70ae0c79.js +41 -0
  13. flowfile/web/static/assets/ContextMenu-c13f91d0.css +26 -0
  14. flowfile/web/static/assets/ContextMenu-f149cf7c.js +41 -0
  15. flowfile/web/static/assets/{CrossJoin-41efa4cb.css → CrossJoin-1119d18e.css} +18 -18
  16. flowfile/web/static/assets/{CrossJoin-9e156ebe.js → CrossJoin-702a3edd.js} +14 -84
  17. flowfile/web/static/assets/CustomNode-74a37f74.css +32 -0
  18. flowfile/web/static/assets/CustomNode-b1519993.js +211 -0
  19. flowfile/web/static/assets/{DatabaseConnectionSettings-d5c625b3.js → DatabaseConnectionSettings-6f3e4ea5.js} +3 -3
  20. flowfile/web/static/assets/{DatabaseManager-265adc5e.js → DatabaseManager-cf5ef661.js} +2 -2
  21. flowfile/web/static/assets/{DatabaseReader-f50c6558.css → DatabaseReader-ae61773c.css} +0 -27
  22. flowfile/web/static/assets/{DatabaseReader-0b10551e.js → DatabaseReader-d38c7295.js} +14 -114
  23. flowfile/web/static/assets/{DatabaseWriter-c17c6916.js → DatabaseWriter-b04ef46a.js} +13 -74
  24. flowfile/web/static/assets/{ExploreData-5bdae813.css → ExploreData-2d0cf4db.css} +8 -14
  25. flowfile/web/static/assets/ExploreData-5fa10ed8.js +192 -0
  26. flowfile/web/static/assets/{ExternalSource-3a66556c.js → ExternalSource-d39af878.js} +8 -79
  27. flowfile/web/static/assets/{Filter-91ad87e7.js → Filter-9b6d08db.js} +12 -85
  28. flowfile/web/static/assets/{Filter-a9d08ba1.css → Filter-f62091b3.css} +3 -3
  29. flowfile/web/static/assets/{Formula-3c395ab1.js → Formula-6b04fb1d.js} +20 -87
  30. flowfile/web/static/assets/{Formula-29f19d21.css → Formula-bb96803d.css} +4 -4
  31. flowfile/web/static/assets/{FuzzyMatch-6857de82.css → FuzzyMatch-1010f966.css} +42 -42
  32. flowfile/web/static/assets/{FuzzyMatch-2df0d230.js → FuzzyMatch-999521f4.js} +16 -87
  33. flowfile/web/static/assets/{GraphSolver-d285877f.js → GraphSolver-17dd2198.js} +13 -159
  34. flowfile/web/static/assets/GraphSolver-f0cb7bfb.css +22 -0
  35. flowfile/web/static/assets/{GroupBy-0bd1cc6b.js → GroupBy-6b039e18.js} +12 -75
  36. flowfile/web/static/assets/{Unique-b5615727.css → GroupBy-b9505323.css} +8 -8
  37. flowfile/web/static/assets/{Join-5a78a203.js → Join-24d0f113.js} +15 -85
  38. flowfile/web/static/assets/{Join-f45eff22.css → Join-fd79b451.css} +20 -20
  39. flowfile/web/static/assets/{ManualInput-a71b52c6.css → ManualInput-3246a08d.css} +20 -20
  40. flowfile/web/static/assets/{ManualInput-93aef9d6.js → ManualInput-34639209.js} +11 -82
  41. flowfile/web/static/assets/MultiSelect-0e8724a3.js +5 -0
  42. flowfile/web/static/assets/MultiSelect.vue_vue_type_script_setup_true_lang-b0e538c2.js +63 -0
  43. flowfile/web/static/assets/NumericInput-3d63a470.js +5 -0
  44. flowfile/web/static/assets/NumericInput.vue_vue_type_script_setup_true_lang-e0edeccc.js +35 -0
  45. flowfile/web/static/assets/Output-283fe388.css +37 -0
  46. flowfile/web/static/assets/{Output-411ecaee.js → Output-edea9802.js} +62 -273
  47. flowfile/web/static/assets/{Pivot-89db4b04.js → Pivot-61d19301.js} +14 -138
  48. flowfile/web/static/assets/Pivot-cf333e3d.css +22 -0
  49. flowfile/web/static/assets/PivotValidation-891ddfb0.css +13 -0
  50. flowfile/web/static/assets/PivotValidation-c46cd420.css +13 -0
  51. flowfile/web/static/assets/PivotValidation-de9f43fe.js +61 -0
  52. flowfile/web/static/assets/PivotValidation-f97fec5b.js +61 -0
  53. flowfile/web/static/assets/{PolarsCode-a9f974f8.js → PolarsCode-bc3c9984.js} +13 -80
  54. flowfile/web/static/assets/Read-64a3f259.js +218 -0
  55. flowfile/web/static/assets/Read-e808b239.css +62 -0
  56. flowfile/web/static/assets/RecordCount-3d5039be.js +53 -0
  57. flowfile/web/static/assets/{RecordId-55ae7d36.js → RecordId-597510e0.js} +8 -80
  58. flowfile/web/static/assets/SQLQueryComponent-36cef432.css +27 -0
  59. flowfile/web/static/assets/SQLQueryComponent-df51adbe.js +38 -0
  60. flowfile/web/static/assets/{Sample-b4a18476.js → Sample-4be0a507.js} +8 -77
  61. flowfile/web/static/assets/{SecretManager-b066d13a.js → SecretManager-4839be57.js} +2 -2
  62. flowfile/web/static/assets/{Select-727688dc.js → Select-9b72f201.js} +11 -85
  63. flowfile/web/static/assets/SettingsSection-2e4d03c4.css +21 -0
  64. flowfile/web/static/assets/SettingsSection-5c696bee.css +20 -0
  65. flowfile/web/static/assets/SettingsSection-71e6b7e3.css +21 -0
  66. flowfile/web/static/assets/SettingsSection-7ded385d.js +45 -0
  67. flowfile/web/static/assets/{SettingsSection-695ac487.js → SettingsSection-e1e9c953.js} +2 -40
  68. flowfile/web/static/assets/SettingsSection-f0f75a42.js +53 -0
  69. flowfile/web/static/assets/SingleSelect-6c777aac.js +5 -0
  70. flowfile/web/static/assets/SingleSelect.vue_vue_type_script_setup_true_lang-33e3ff9b.js +62 -0
  71. flowfile/web/static/assets/SliderInput-7cb93e62.js +40 -0
  72. flowfile/web/static/assets/SliderInput-b8fb6a8c.css +4 -0
  73. flowfile/web/static/assets/{GroupBy-ab1ea74b.css → Sort-3643d625.css} +8 -8
  74. flowfile/web/static/assets/{Sort-be3339a8.js → Sort-6cbde21a.js} +12 -97
  75. flowfile/web/static/assets/TextInput-d9a40c11.js +5 -0
  76. flowfile/web/static/assets/TextInput.vue_vue_type_script_setup_true_lang-5896c375.js +32 -0
  77. flowfile/web/static/assets/{TextToRows-c92d1ec2.css → TextToRows-5d2c1190.css} +9 -9
  78. flowfile/web/static/assets/{TextToRows-7b8998da.js → TextToRows-c4fcbf4d.js} +14 -83
  79. flowfile/web/static/assets/ToggleSwitch-4ef91d19.js +5 -0
  80. flowfile/web/static/assets/ToggleSwitch.vue_vue_type_script_setup_true_lang-38478c20.js +31 -0
  81. flowfile/web/static/assets/{UnavailableFields-8b0cb48e.js → UnavailableFields-a03f512c.js} +2 -2
  82. flowfile/web/static/assets/{Union-8d9ac7f9.css → Union-af6c3d9b.css} +6 -6
  83. flowfile/web/static/assets/Union-bfe9b996.js +77 -0
  84. flowfile/web/static/assets/{Unique-af5a80b4.js → Unique-5d023a27.js} +23 -104
  85. flowfile/web/static/assets/{Sort-7ccfa0fe.css → Unique-f9fb0809.css} +8 -8
  86. flowfile/web/static/assets/Unpivot-1e422df3.css +30 -0
  87. flowfile/web/static/assets/{Unpivot-5195d411.js → Unpivot-91cc5354.js} +12 -166
  88. flowfile/web/static/assets/UnpivotValidation-0d240eeb.css +13 -0
  89. flowfile/web/static/assets/UnpivotValidation-7ee2de44.js +51 -0
  90. flowfile/web/static/assets/{ExploreData-18a4fe52.js → VueGraphicWalker-e51b9924.js} +4 -264
  91. flowfile/web/static/assets/VueGraphicWalker-ed5ab88b.css +6 -0
  92. flowfile/web/static/assets/{api-cb00cce6.js → api-c1bad5ca.js} +1 -1
  93. flowfile/web/static/assets/{api-023d1733.js → api-cf1221f0.js} +1 -1
  94. flowfile/web/static/assets/{designer-2197d782.css → designer-8da3ba3a.css} +859 -201
  95. flowfile/web/static/assets/{designer-6c322d8e.js → designer-9633482a.js} +2297 -733
  96. flowfile/web/static/assets/{documentation-4d1fafe1.js → documentation-ca400224.js} +1 -1
  97. flowfile/web/static/assets/{dropDown-0b46dd77.js → dropDown-614b998d.js} +1 -1
  98. flowfile/web/static/assets/{fullEditor-ec4e4f95.js → fullEditor-f7971590.js} +2 -2
  99. flowfile/web/static/assets/{genericNodeSettings-def5879b.js → genericNodeSettings-4fe5f36b.js} +3 -3
  100. flowfile/web/static/assets/{index-681a3ed0.css → index-50508d4d.css} +8 -0
  101. flowfile/web/static/assets/{index-683fc198.js → index-5429bbf8.js} +208 -31
  102. flowfile/web/static/assets/nodeInput-5d0d6b79.js +41 -0
  103. flowfile/web/static/assets/outputCsv-076b85ab.js +86 -0
  104. flowfile/web/static/assets/{Output-48f81019.css → outputCsv-9cc59e0b.css} +0 -143
  105. flowfile/web/static/assets/outputExcel-0fd17dbe.js +56 -0
  106. flowfile/web/static/assets/outputExcel-b41305c0.css +102 -0
  107. flowfile/web/static/assets/outputParquet-b61e0847.js +31 -0
  108. flowfile/web/static/assets/outputParquet-cf8cf3f2.css +4 -0
  109. flowfile/web/static/assets/readCsv-a8bb8b61.js +179 -0
  110. flowfile/web/static/assets/readCsv-c767cb37.css +52 -0
  111. flowfile/web/static/assets/readExcel-67b4aee0.js +201 -0
  112. flowfile/web/static/assets/readExcel-806d2826.css +64 -0
  113. flowfile/web/static/assets/readParquet-48c81530.css +19 -0
  114. flowfile/web/static/assets/readParquet-92ce1dbc.js +23 -0
  115. flowfile/web/static/assets/{secretApi-baceb6f9.js → secretApi-68435402.js} +1 -1
  116. flowfile/web/static/assets/{selectDynamic-de91449a.js → selectDynamic-92e25ee3.js} +7 -7
  117. flowfile/web/static/assets/{selectDynamic-b062bc9b.css → selectDynamic-aa913ff4.css} +16 -16
  118. flowfile/web/static/assets/user-defined-icon-0ae16c90.png +0 -0
  119. flowfile/web/static/assets/{vue-codemirror.esm-dc5e3348.js → vue-codemirror.esm-41b0e0d7.js} +65 -36
  120. flowfile/web/static/assets/{vue-content-loader.es-ba94b82f.js → vue-content-loader.es-2c8e608f.js} +1 -1
  121. flowfile/web/static/index.html +2 -2
  122. {flowfile-0.3.9.dist-info → flowfile-0.5.1.dist-info}/METADATA +5 -3
  123. {flowfile-0.3.9.dist-info → flowfile-0.5.1.dist-info}/RECORD +191 -121
  124. {flowfile-0.3.9.dist-info → flowfile-0.5.1.dist-info}/WHEEL +1 -1
  125. {flowfile-0.3.9.dist-info → flowfile-0.5.1.dist-info}/entry_points.txt +1 -0
  126. flowfile_core/__init__.py +3 -0
  127. flowfile_core/configs/flow_logger.py +5 -13
  128. flowfile_core/configs/node_store/__init__.py +30 -0
  129. flowfile_core/configs/node_store/nodes.py +383 -99
  130. flowfile_core/configs/node_store/user_defined_node_registry.py +193 -0
  131. flowfile_core/configs/settings.py +2 -1
  132. flowfile_core/database/connection.py +5 -21
  133. flowfile_core/fileExplorer/funcs.py +239 -121
  134. flowfile_core/flowfile/analytics/analytics_processor.py +1 -0
  135. flowfile_core/flowfile/code_generator/code_generator.py +62 -64
  136. flowfile_core/flowfile/flow_data_engine/create/funcs.py +73 -56
  137. flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +77 -86
  138. flowfile_core/flowfile/flow_data_engine/flow_file_column/interface.py +4 -0
  139. flowfile_core/flowfile/flow_data_engine/flow_file_column/main.py +19 -34
  140. flowfile_core/flowfile/flow_data_engine/flow_file_column/type_registry.py +36 -0
  141. flowfile_core/flowfile/flow_data_engine/fuzzy_matching/prepare_for_fuzzy_match.py +23 -23
  142. flowfile_core/flowfile/flow_data_engine/join/utils.py +1 -1
  143. flowfile_core/flowfile/flow_data_engine/join/verify_integrity.py +9 -4
  144. flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +212 -86
  145. flowfile_core/flowfile/flow_data_engine/utils.py +2 -0
  146. flowfile_core/flowfile/flow_graph.py +240 -54
  147. flowfile_core/flowfile/flow_node/flow_node.py +48 -13
  148. flowfile_core/flowfile/flow_node/models.py +2 -1
  149. flowfile_core/flowfile/handler.py +24 -5
  150. flowfile_core/flowfile/manage/compatibility_enhancements.py +404 -41
  151. flowfile_core/flowfile/manage/io_flowfile.py +394 -0
  152. flowfile_core/flowfile/node_designer/__init__.py +47 -0
  153. flowfile_core/flowfile/node_designer/_type_registry.py +197 -0
  154. flowfile_core/flowfile/node_designer/custom_node.py +371 -0
  155. flowfile_core/flowfile/node_designer/ui_components.py +277 -0
  156. flowfile_core/flowfile/schema_callbacks.py +17 -10
  157. flowfile_core/flowfile/setting_generator/settings.py +15 -10
  158. flowfile_core/main.py +5 -1
  159. flowfile_core/routes/routes.py +73 -30
  160. flowfile_core/routes/user_defined_components.py +55 -0
  161. flowfile_core/schemas/cloud_storage_schemas.py +0 -2
  162. flowfile_core/schemas/input_schema.py +228 -65
  163. flowfile_core/schemas/output_model.py +5 -2
  164. flowfile_core/schemas/schemas.py +153 -35
  165. flowfile_core/schemas/transform_schema.py +1083 -412
  166. flowfile_core/schemas/yaml_types.py +103 -0
  167. flowfile_core/types.py +156 -0
  168. flowfile_core/utils/validate_setup.py +3 -1
  169. flowfile_frame/__init__.py +3 -1
  170. flowfile_frame/flow_frame.py +31 -24
  171. flowfile_frame/flow_frame_methods.py +12 -9
  172. flowfile_worker/__init__.py +9 -35
  173. flowfile_worker/create/__init__.py +3 -21
  174. flowfile_worker/create/funcs.py +68 -56
  175. flowfile_worker/create/models.py +130 -62
  176. flowfile_worker/main.py +5 -2
  177. flowfile_worker/routes.py +52 -13
  178. shared/__init__.py +15 -0
  179. shared/storage_config.py +258 -0
  180. tools/migrate/README.md +56 -0
  181. tools/migrate/__init__.py +12 -0
  182. tools/migrate/__main__.py +131 -0
  183. tools/migrate/legacy_schemas.py +621 -0
  184. tools/migrate/migrate.py +598 -0
  185. tools/migrate/tests/__init__.py +0 -0
  186. tools/migrate/tests/conftest.py +23 -0
  187. tools/migrate/tests/test_migrate.py +627 -0
  188. tools/migrate/tests/test_migration_e2e.py +1010 -0
  189. tools/migrate/tests/test_node_migrations.py +813 -0
  190. flowfile/web/static/assets/GraphSolver-17fd26db.css +0 -68
  191. flowfile/web/static/assets/Pivot-f415e85f.css +0 -35
  192. flowfile/web/static/assets/Read-80dc1675.css +0 -197
  193. flowfile/web/static/assets/Read-c3b1929c.js +0 -701
  194. flowfile/web/static/assets/RecordCount-4e95f98e.js +0 -122
  195. flowfile/web/static/assets/Union-89fd73dc.js +0 -146
  196. flowfile/web/static/assets/Unpivot-246e9bbd.css +0 -77
  197. flowfile/web/static/assets/nodeTitle-a16db7c3.js +0 -227
  198. flowfile/web/static/assets/nodeTitle-f4b12bcb.css +0 -134
  199. flowfile_core/flowfile/manage/open_flowfile.py +0 -135
  200. {flowfile-0.3.9.dist-info → flowfile-0.5.1.dist-info/licenses}/LICENSE +0 -0
  201. /flowfile_core/flowfile/manage/manage_flowfile.py → /tools/__init__.py +0 -0
@@ -0,0 +1,258 @@
1
+ # shared/storage_config.py - Updated for Option 3
2
+ """
3
+ Centralized storage configuration for Flowfile.
4
+ This module can be imported by both core and worker without creating dependencies.
5
+ """
6
+ import os
7
+ from pathlib import Path
8
+ from typing import Optional, Literal
9
+
10
+ DirectoryOptions = Literal["temp_directory", "logs_directory",
11
+ "system_logs_directory", "database_directory",
12
+ "cache_directory", "flows_directory", "user_defined_nodes_directory"]
13
+
14
+
15
+ class FlowfileStorage:
16
+ """Centralized storage manager for Flowfile applications."""
17
+
18
+ def __init__(self):
19
+ self._base_dir: Optional[Path] = None
20
+ self._user_data_dir: Optional[Path] = None
21
+ self._ensure_directories()
22
+
23
+ @property
24
+ def base_directory(self) -> Path:
25
+ """Get the base Flowfile storage directory (for internal container communication)."""
26
+ if self._base_dir is None:
27
+ if os.environ.get("RUNNING_IN_DOCKER") == "true":
28
+ # In Docker, internal storage stays inside /app
29
+ base_path = os.environ.get("FLOWFILE_STORAGE_DIR", "/app/internal_storage")
30
+ else:
31
+ # Local development
32
+ base_path = os.environ.get("FLOWFILE_STORAGE_DIR")
33
+ if not base_path:
34
+ home_dir = Path.home()
35
+ base_path = home_dir / ".flowfile"
36
+
37
+ self._base_dir = Path(base_path)
38
+ return self._base_dir
39
+
40
+ @property
41
+ def user_data_directory(self) -> Path:
42
+ """Get the user data directory (completely separate from application code)."""
43
+ if self._user_data_dir is None:
44
+ if os.environ.get("RUNNING_IN_DOCKER") == "true":
45
+ # In Docker, user data is at /data/user (completely outside /app)
46
+ user_data_path = os.environ.get("FLOWFILE_USER_DATA_DIR", "/data/user")
47
+ else:
48
+ # Local development - use user's home directory
49
+ user_data_path = Path.home()
50
+
51
+ self._user_data_dir = Path(user_data_path)
52
+ return self._user_data_dir
53
+
54
+ @property
55
+ def cache_directory(self) -> Path:
56
+ """Cache directory for worker-core communication (internal)."""
57
+ return self.base_directory / "cache"
58
+
59
+ def get_flow_cache_directory(self, flow_id: int) -> Path:
60
+ """Get or create a cache directory for a specific flow (internal)."""
61
+ flow_cache_dir = self.cache_directory / str(flow_id)
62
+ flow_cache_dir.mkdir(parents=True, exist_ok=True)
63
+ return flow_cache_dir
64
+
65
+ @property
66
+ def system_logs_directory(self) -> Path:
67
+ """Directory for system logs (internal)."""
68
+ return self.base_directory / "system_logs"
69
+
70
+ @property
71
+ def flows_directory(self) -> Path:
72
+ """Directory for flow storage (user-accessible)."""
73
+ if os.environ.get("RUNNING_IN_DOCKER") == "true":
74
+ # In Docker, flows are in separate user data area
75
+ return self.user_data_directory / "flows"
76
+ else:
77
+ # Local development - flows in ~/.flowfile/flows
78
+ return self.base_directory / "flows"
79
+
80
+ @property
81
+ def uploads_directory(self) -> Path:
82
+ """Directory for user uploads (user-accessible)."""
83
+ if os.environ.get("RUNNING_IN_DOCKER") == "true":
84
+ # In Docker, uploads are in separate user data area
85
+ return self.user_data_directory / "uploads"
86
+ else:
87
+ # Local development - uploads in ~/.flowfile/uploads
88
+ return self.base_directory / "uploads"
89
+
90
+ @property
91
+ def user_defined_nodes_directory(self) -> Path:
92
+ """Directory for user-defined custom nodes (user-accessible)."""
93
+ if os.environ.get("RUNNING_IN_DOCKER") == "true":
94
+ return self.user_data_directory / "user_defined_nodes"
95
+ else:
96
+ return self.base_directory / "user_defined_nodes"
97
+
98
+ @property
99
+ def user_defined_nodes_icons(self) -> Path:
100
+ """Directory for user-defined custom node icon (user-accessible)."""
101
+ return self.user_defined_nodes_directory / "icons"
102
+
103
+ @property
104
+ def outputs_directory(self) -> Path:
105
+ """Directory for user outputs (user-accessible)."""
106
+ if os.environ.get("RUNNING_IN_DOCKER") == "true":
107
+ # In Docker, outputs are in separate user data area
108
+ return self.user_data_directory / "outputs"
109
+ else:
110
+ # Local development - outputs in ~/.flowfile/outputs
111
+ return self.base_directory / "outputs"
112
+
113
+ @property
114
+ def database_directory(self) -> Path:
115
+ """Directory for local database files (internal)."""
116
+ return self.base_directory / "database"
117
+
118
+ @property
119
+ def logs_directory(self) -> Path:
120
+ """Directory for application logs (internal)."""
121
+ return self.base_directory / "logs"
122
+
123
+ @property
124
+ def temp_directory(self) -> Path:
125
+ """Directory for temporary files (internal)."""
126
+ return self.base_directory / "temp"
127
+
128
+ @property
129
+ def temp_directory_for_flows(self) -> Path:
130
+ """Directory for temporary files specific to flows (internal)."""
131
+ return self.temp_directory / "flows"
132
+
133
+ def _ensure_directories(self) -> None:
134
+ """Create all necessary directories if they don't exist."""
135
+ # Internal directories (always created in base_directory)
136
+ internal_directories = [
137
+ self.cache_directory,
138
+ self.database_directory,
139
+ self.logs_directory,
140
+ self.temp_directory,
141
+ self.system_logs_directory,
142
+ self.temp_directory_for_flows,
143
+ ]
144
+
145
+ # User-accessible directories (location depends on environment)
146
+ user_directories = [
147
+ self.flows_directory,
148
+ self.uploads_directory,
149
+ self.outputs_directory,
150
+ self.user_defined_nodes_directory,
151
+ self.user_defined_nodes_icons,
152
+ ]
153
+
154
+ for directory in internal_directories + user_directories:
155
+ directory.mkdir(parents=True, exist_ok=True)
156
+
157
+ def get_cache_file_path(self, filename: str) -> Path:
158
+ """Get full path for a cache file (internal)."""
159
+ return self.cache_directory / filename
160
+
161
+ def get_flow_file_path(self, filename: str) -> Path:
162
+ """Get full path for a flow file (user-accessible)."""
163
+ return self.flows_directory / filename
164
+
165
+ def get_upload_file_path(self, filename: str) -> Path:
166
+ """Get full path for an uploaded file (user-accessible)."""
167
+ return self.uploads_directory / filename
168
+
169
+ def get_output_file_path(self, filename: str) -> Path:
170
+ """Get full path for an output file (user-accessible)."""
171
+ return self.outputs_directory / filename
172
+
173
+ def get_log_file_path(self, filename: str) -> Path:
174
+ """Get full path for an application log file (internal)."""
175
+ return self.logs_directory / filename
176
+
177
+ def get_system_log_file_path(self, filename: str) -> Path:
178
+ """Get full path for a system log file (internal)."""
179
+ return self.system_logs_directory / filename
180
+
181
+ def get_temp_file_path(self, filename: str) -> Path:
182
+ """Get full path for a temporary file (internal)."""
183
+ return self.temp_directory / filename
184
+
185
+ def cleanup_directory(self, directory_option: DirectoryOptions, storage_duration_hours: int = 24) -> None:
186
+ """Clean up any directory of the folder"""
187
+ import time
188
+ import shutil
189
+
190
+ if not hasattr(self, directory_option):
191
+ raise Exception(f"Directory does not exist in {self.base_directory}")
192
+
193
+ directory = getattr(self, directory_option)
194
+ if not isinstance(directory, Path):
195
+ raise Exception(f"Directory attribute {directory_option} is not a Path object")
196
+
197
+ if not directory.exists():
198
+ return
199
+
200
+ current_time = time.time()
201
+ cutoff_time = current_time - (storage_duration_hours * 60 * 60)
202
+
203
+ for item in directory.iterdir():
204
+ try:
205
+ if item.stat().st_mtime < cutoff_time:
206
+ if item.is_file():
207
+ item.unlink()
208
+ elif item.is_dir():
209
+ shutil.rmtree(item)
210
+ except (OSError, FileNotFoundError):
211
+ # Handle permission errors or files that disappeared
212
+ continue
213
+
214
+ def cleanup_directories(self) -> None:
215
+ """Clean up temporary files older than specified hours."""
216
+ self.cleanup_directory("temp_directory", storage_duration_hours=24)
217
+ self.cleanup_directory("cache_directory", storage_duration_hours=1)
218
+ self.cleanup_directory("logs_directory", storage_duration_hours=168)
219
+ self.cleanup_directory("system_logs_directory", storage_duration_hours=168)
220
+
221
+
222
+ storage = FlowfileStorage()
223
+
224
+
225
+ # Convenience functions for backward compatibility
226
+ def get_cache_directory() -> str:
227
+ """Get cache directory path as string."""
228
+ return str(storage.cache_directory)
229
+
230
+
231
+ def get_temp_directory() -> str:
232
+ """Get temp directory path as string."""
233
+ return str(storage.temp_directory)
234
+
235
+
236
+ def get_flows_directory() -> str:
237
+ """Get flows directory path as string."""
238
+ return str(storage.flows_directory)
239
+
240
+
241
+ def get_uploads_directory() -> str:
242
+ """Get uploads directory path as string."""
243
+ return str(storage.uploads_directory)
244
+
245
+
246
+ def get_outputs_directory() -> str:
247
+ """Get outputs directory path as string."""
248
+ return str(storage.outputs_directory)
249
+
250
+
251
+ def get_logs_directory() -> str:
252
+ """Get application logs directory path as string."""
253
+ return str(storage.logs_directory)
254
+
255
+
256
+ def get_system_logs_directory() -> str:
257
+ """Get system logs directory path as string."""
258
+ return str(storage.system_logs_directory)
@@ -0,0 +1,56 @@
1
+ # Flowfile Migration Tool
2
+
3
+ Migrates `.flowfile` (pickle format, used in v0.4.1 and earlier) to YAML (v0.5+).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install pyyaml # Required for YAML output
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ # Single file
15
+ python -m tools.migrate path/to/flow.flowfile
16
+
17
+ # Directory (recursive)
18
+ python -m tools.migrate ./flows/
19
+
20
+ # Specify output path
21
+ python -m tools.migrate flow.flowfile -o /output/path/flow.yaml
22
+
23
+ # Output as JSON
24
+ python -m tools.migrate flow.flowfile --format json
25
+
26
+ # Dry run
27
+ python -m tools.migrate ./flows/ --dry-run
28
+
29
+ # Verbose (show tracebacks)
30
+ python -m tools.migrate flow.flowfile -v
31
+ ```
32
+
33
+ ## Output Structure
34
+
35
+ ```yaml
36
+ _version: '2.0'
37
+ _migrated_from: pickle
38
+ flow_id: 1
39
+ flow_name: my_analysis
40
+ flow_settings:
41
+ name: my_analysis
42
+ description: null
43
+ execution_mode: Development
44
+ nodes:
45
+ - id: 1
46
+ type: read
47
+ position: {x: 100, y: 200}
48
+ settings:
49
+ received_file:
50
+ path: data/input.csv
51
+ file_type: csv
52
+ connections:
53
+ - [1, 2]
54
+ node_starts:
55
+ - 1
56
+ ```
@@ -0,0 +1,12 @@
1
+ """
2
+ Flowfile Migration Tool
3
+
4
+ Converts old pickle-based .flowfile format to new YAML format.
5
+
6
+ Usage:
7
+ python -m tools.migrate <path>
8
+ python -m tools.migrate old_flow.flowfile
9
+ python -m tools.migrate ./flows/ # migrate entire directory
10
+ """
11
+
12
+ __version__ = "1.0.0"
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ Flowfile Migration Tool - CLI Entry Point
4
+
5
+ Converts old pickle-based .flowfile format to new YAML format.
6
+
7
+ Usage:
8
+ python -m tools.migrate <path> [options]
9
+
10
+ Examples:
11
+ # Migrate a single file
12
+ python -m tools.migrate my_flow.flowfile
13
+
14
+ # Migrate to specific output
15
+ python -m tools.migrate my_flow.flowfile -o my_flow.yaml
16
+
17
+ # Migrate entire directory
18
+ python -m tools.migrate ./flows/
19
+
20
+ # Migrate to JSON instead of YAML
21
+ python -m tools.migrate my_flow.flowfile --format json
22
+
23
+ # Migrate directory to different output location
24
+ python -m tools.migrate ./old_flows/ -o ./new_flows/
25
+ """
26
+
27
+ import argparse
28
+ import sys
29
+ from pathlib import Path
30
+
31
+ from tools.migrate.migrate import migrate_flowfile, migrate_directory
32
+
33
+
34
+ def main():
35
+ parser = argparse.ArgumentParser(
36
+ prog='flowfile-migrate',
37
+ description='Migrate old .flowfile pickles to YAML format',
38
+ formatter_class=argparse.RawDescriptionHelpFormatter,
39
+ epilog="""
40
+ Examples:
41
+ %(prog)s my_flow.flowfile Migrate single file to YAML
42
+ %(prog)s ./flows/ Migrate all files in directory
43
+ %(prog)s flow.flowfile -o flow.yaml Specify output path
44
+ %(prog)s ./flows/ --format json Output as JSON instead of YAML
45
+ """
46
+ )
47
+
48
+ parser.add_argument(
49
+ 'path',
50
+ type=Path,
51
+ help='Path to .flowfile or directory containing .flowfile files'
52
+ )
53
+
54
+ parser.add_argument(
55
+ '-o', '--output',
56
+ type=Path,
57
+ default=None,
58
+ help='Output path (file or directory). Default: same location with new extension'
59
+ )
60
+
61
+ parser.add_argument(
62
+ '-f', '--format',
63
+ choices=['yaml', 'json'],
64
+ default='yaml',
65
+ help='Output format (default: yaml)'
66
+ )
67
+
68
+ parser.add_argument(
69
+ '-v', '--verbose',
70
+ action='store_true',
71
+ help='Verbose output'
72
+ )
73
+
74
+ parser.add_argument(
75
+ '--dry-run',
76
+ action='store_true',
77
+ help='Show what would be migrated without actually migrating'
78
+ )
79
+
80
+ args = parser.parse_args()
81
+
82
+ # Validate input path
83
+ if not args.path.exists():
84
+ print(f"Error: Path not found: {args.path}", file=sys.stderr)
85
+ sys.exit(1)
86
+
87
+ # Dry run mode
88
+ if args.dry_run:
89
+ if args.path.is_file():
90
+ print(f"Would migrate: {args.path}")
91
+ suffix = '.yaml' if args.format == 'yaml' else '.json'
92
+ output = args.output or args.path.with_suffix(suffix)
93
+ print(f" → {output}")
94
+ else:
95
+ flowfiles = list(args.path.glob('**/*.flowfile'))
96
+ print(f"Would migrate {len(flowfiles)} file(s):")
97
+ for f in flowfiles:
98
+ print(f" - {f}")
99
+ sys.exit(0)
100
+
101
+ # Check for yaml dependency
102
+ if args.format == 'yaml':
103
+ try:
104
+ import yaml
105
+ except ImportError:
106
+ print("Error: PyYAML is required for YAML output.", file=sys.stderr)
107
+ print("Install with: pip install pyyaml", file=sys.stderr)
108
+ sys.exit(1)
109
+
110
+ # Run migration
111
+ try:
112
+ if args.path.is_file():
113
+ migrate_flowfile(args.path, args.output, args.format)
114
+ elif args.path.is_dir():
115
+ migrate_directory(args.path, args.output, args.format)
116
+ else:
117
+ print(f"Error: {args.path} is neither a file nor a directory", file=sys.stderr)
118
+ sys.exit(1)
119
+
120
+ except Exception as e:
121
+ print(f"Error: {e}", file=sys.stderr)
122
+ if args.verbose:
123
+ import traceback
124
+ traceback.print_exc()
125
+ sys.exit(1)
126
+
127
+ print("\nMigration complete!")
128
+
129
+
130
+ if __name__ == '__main__':
131
+ main()