Flowfile 0.3.0__py3-none-any.whl → 0.3.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of Flowfile might be problematic. Click here for more details.

Files changed (149) hide show
  1. flowfile/__init__.py +13 -6
  2. flowfile/__main__.py +50 -15
  3. flowfile/api.py +383 -0
  4. flowfile/readme.md +130 -0
  5. flowfile/web/__init__.py +155 -0
  6. flowfile/web/static/assets/AirbyteReader-1ac35765.css +314 -0
  7. flowfile/web/static/assets/AirbyteReader-cb0c1d4a.js +921 -0
  8. flowfile/web/static/assets/CrossJoin-41efa4cb.css +100 -0
  9. flowfile/web/static/assets/CrossJoin-a514fa59.js +153 -0
  10. flowfile/web/static/assets/DatabaseConnectionSettings-0c04b2e5.css +77 -0
  11. flowfile/web/static/assets/DatabaseConnectionSettings-f2cecf33.js +151 -0
  12. flowfile/web/static/assets/DatabaseManager-30fa27e5.css +64 -0
  13. flowfile/web/static/assets/DatabaseManager-83ee3c98.js +484 -0
  14. flowfile/web/static/assets/DatabaseReader-dc0c6881.js +426 -0
  15. flowfile/web/static/assets/DatabaseReader-f50c6558.css +158 -0
  16. flowfile/web/static/assets/DatabaseWriter-2f570e53.css +96 -0
  17. flowfile/web/static/assets/DatabaseWriter-5afe9f8d.js +312 -0
  18. flowfile/web/static/assets/ExploreData-5bdae813.css +45 -0
  19. flowfile/web/static/assets/ExploreData-c7ee19cf.js +118306 -0
  20. flowfile/web/static/assets/ExternalSource-17b23a01.js +225 -0
  21. flowfile/web/static/assets/ExternalSource-e37b6275.css +94 -0
  22. flowfile/web/static/assets/Filter-90856b4f.js +238 -0
  23. flowfile/web/static/assets/Filter-a9d08ba1.css +20 -0
  24. flowfile/web/static/assets/Formula-38b71e9e.js +197 -0
  25. flowfile/web/static/assets/Formula-d60a74f4.css +17 -0
  26. flowfile/web/static/assets/FuzzyMatch-6857de82.css +254 -0
  27. flowfile/web/static/assets/FuzzyMatch-d0f1fe81.js +422 -0
  28. flowfile/web/static/assets/GoogleSheet-854294a4.js +2616 -0
  29. flowfile/web/static/assets/GoogleSheet-92084da7.css +233 -0
  30. flowfile/web/static/assets/GraphSolver-0c86bbc6.js +382 -0
  31. flowfile/web/static/assets/GraphSolver-17fd26db.css +68 -0
  32. flowfile/web/static/assets/GroupBy-ab1ea74b.css +51 -0
  33. flowfile/web/static/assets/GroupBy-f2772e9f.js +413 -0
  34. flowfile/web/static/assets/Join-41c0f331.css +109 -0
  35. flowfile/web/static/assets/Join-bc3e1cf7.js +247 -0
  36. flowfile/web/static/assets/ManualInput-03aa0245.js +391 -0
  37. flowfile/web/static/assets/ManualInput-ac7b9972.css +84 -0
  38. flowfile/web/static/assets/Output-48f81019.css +2642 -0
  39. flowfile/web/static/assets/Output-5b35eee8.js +536 -0
  40. flowfile/web/static/assets/Pivot-7164087c.js +408 -0
  41. flowfile/web/static/assets/Pivot-f415e85f.css +35 -0
  42. flowfile/web/static/assets/PolarsCode-3abf6507.js +2863 -0
  43. flowfile/web/static/assets/PolarsCode-650322d1.css +35 -0
  44. flowfile/web/static/assets/PopOver-b37ff9be.js +577 -0
  45. flowfile/web/static/assets/PopOver-bccfde04.css +32 -0
  46. flowfile/web/static/assets/Read-65966a3e.js +701 -0
  47. flowfile/web/static/assets/Read-80dc1675.css +197 -0
  48. flowfile/web/static/assets/RecordCount-c66c6d6d.js +121 -0
  49. flowfile/web/static/assets/RecordId-826dc095.js +339 -0
  50. flowfile/web/static/assets/Sample-4ed555c8.js +184 -0
  51. flowfile/web/static/assets/SecretManager-eac1e97d.js +382 -0
  52. flowfile/web/static/assets/Select-085f05cc.js +231 -0
  53. flowfile/web/static/assets/SettingsSection-1f5e79c1.js +87 -0
  54. flowfile/web/static/assets/SettingsSection-9c836ecc.css +47 -0
  55. flowfile/web/static/assets/Sort-3e6cb414.js +309 -0
  56. flowfile/web/static/assets/Sort-7ccfa0fe.css +51 -0
  57. flowfile/web/static/assets/TextToRows-606349bc.js +307 -0
  58. flowfile/web/static/assets/TextToRows-c92d1ec2.css +48 -0
  59. flowfile/web/static/assets/UnavailableFields-5edd5322.css +49 -0
  60. flowfile/web/static/assets/UnavailableFields-b41976ed.js +36 -0
  61. flowfile/web/static/assets/Union-8d9ac7f9.css +30 -0
  62. flowfile/web/static/assets/Union-fca91665.js +145 -0
  63. flowfile/web/static/assets/Unique-a59f830e.js +273 -0
  64. flowfile/web/static/assets/Unique-b5615727.css +51 -0
  65. flowfile/web/static/assets/Unpivot-246e9bbd.css +77 -0
  66. flowfile/web/static/assets/Unpivot-c3815565.js +441 -0
  67. flowfile/web/static/assets/airbyte-292aa232.png +0 -0
  68. flowfile/web/static/assets/api-22b338bd.js +60 -0
  69. flowfile/web/static/assets/cross_join-d30c0290.png +0 -0
  70. flowfile/web/static/assets/database_reader-ce1e55f3.svg +24 -0
  71. flowfile/web/static/assets/database_writer-b4ad0753.svg +23 -0
  72. flowfile/web/static/assets/designer-2394122a.css +10697 -0
  73. flowfile/web/static/assets/designer-e5bbe26f.js +69712 -0
  74. flowfile/web/static/assets/documentation-08045cf2.js +33 -0
  75. flowfile/web/static/assets/documentation-12216a74.css +50 -0
  76. flowfile/web/static/assets/dropDown-35135ba8.css +143 -0
  77. flowfile/web/static/assets/dropDown-5e7e9a5a.js +319 -0
  78. flowfile/web/static/assets/dropDownGeneric-50a91b99.js +72 -0
  79. flowfile/web/static/assets/dropDownGeneric-895680d6.css +10 -0
  80. flowfile/web/static/assets/element-icons-9c88a535.woff +0 -0
  81. flowfile/web/static/assets/element-icons-de5eb258.ttf +0 -0
  82. flowfile/web/static/assets/explore_data-8a0a2861.png +0 -0
  83. flowfile/web/static/assets/fa-brands-400-808443ae.ttf +0 -0
  84. flowfile/web/static/assets/fa-brands-400-d7236a19.woff2 +0 -0
  85. flowfile/web/static/assets/fa-regular-400-54cf6086.ttf +0 -0
  86. flowfile/web/static/assets/fa-regular-400-e3456d12.woff2 +0 -0
  87. flowfile/web/static/assets/fa-solid-900-aa759986.woff2 +0 -0
  88. flowfile/web/static/assets/fa-solid-900-d2f05935.ttf +0 -0
  89. flowfile/web/static/assets/fa-v4compatibility-0ce9033c.woff2 +0 -0
  90. flowfile/web/static/assets/fa-v4compatibility-30f6abf6.ttf +0 -0
  91. flowfile/web/static/assets/filter-d7708bda.png +0 -0
  92. flowfile/web/static/assets/formula-eeeb1611.png +0 -0
  93. flowfile/web/static/assets/fullEditor-178376bb.css +256 -0
  94. flowfile/web/static/assets/fullEditor-705c6ccb.js +630 -0
  95. flowfile/web/static/assets/fuzzy_match-40c161b2.png +0 -0
  96. flowfile/web/static/assets/genericNodeSettings-65587f20.js +137 -0
  97. flowfile/web/static/assets/genericNodeSettings-924759c7.css +46 -0
  98. flowfile/web/static/assets/graph_solver-8b7888b8.png +0 -0
  99. flowfile/web/static/assets/group_by-80561fc3.png +0 -0
  100. flowfile/web/static/assets/index-552863fd.js +58652 -0
  101. flowfile/web/static/assets/index-681a3ed0.css +8843 -0
  102. flowfile/web/static/assets/input_data-ab2eb678.png +0 -0
  103. flowfile/web/static/assets/join-349043ae.png +0 -0
  104. flowfile/web/static/assets/manual_input-ae98f31d.png +0 -0
  105. flowfile/web/static/assets/nodeTitle-cf9bae3c.js +227 -0
  106. flowfile/web/static/assets/nodeTitle-f4b12bcb.css +134 -0
  107. flowfile/web/static/assets/old_join-5d0eb604.png +0 -0
  108. flowfile/web/static/assets/output-06ec0371.png +0 -0
  109. flowfile/web/static/assets/pivot-9660df51.png +0 -0
  110. flowfile/web/static/assets/polars_code-05ce5dc6.png +0 -0
  111. flowfile/web/static/assets/record_count-dab44eb5.png +0 -0
  112. flowfile/web/static/assets/record_id-0b15856b.png +0 -0
  113. flowfile/web/static/assets/sample-693a88b5.png +0 -0
  114. flowfile/web/static/assets/secretApi-3ad510e1.js +46 -0
  115. flowfile/web/static/assets/select-b0d0437a.png +0 -0
  116. flowfile/web/static/assets/selectDynamic-b062bc9b.css +107 -0
  117. flowfile/web/static/assets/selectDynamic-bd644891.js +302 -0
  118. flowfile/web/static/assets/sort-2aa579f0.png +0 -0
  119. flowfile/web/static/assets/summarize-2a099231.png +0 -0
  120. flowfile/web/static/assets/text_to_rows-859b29ea.png +0 -0
  121. flowfile/web/static/assets/union-2d8609f4.png +0 -0
  122. flowfile/web/static/assets/unique-1958b98a.png +0 -0
  123. flowfile/web/static/assets/unpivot-d3cb4b5b.png +0 -0
  124. flowfile/web/static/assets/view-7a0f0be1.png +0 -0
  125. flowfile/web/static/assets/vue-codemirror.esm-dd17b478.js +22281 -0
  126. flowfile/web/static/assets/vue-content-loader.es-6b36f05e.js +210 -0
  127. flowfile/web/static/flowfile.svg +47 -0
  128. flowfile/web/static/icons/flowfile.png +0 -0
  129. flowfile/web/static/images/airbyte.png +0 -0
  130. flowfile/web/static/images/flowfile.svg +47 -0
  131. flowfile/web/static/images/google.svg +1 -0
  132. flowfile/web/static/images/sheets.png +0 -0
  133. flowfile/web/static/index.html +22 -0
  134. flowfile/web/static/vite.svg +1 -0
  135. flowfile/web/static/vue.svg +1 -0
  136. flowfile-0.3.0.2.dist-info/METADATA +235 -0
  137. {flowfile-0.3.0.dist-info → flowfile-0.3.0.2.dist-info}/RECORD +147 -15
  138. {flowfile-0.3.0.dist-info → flowfile-0.3.0.2.dist-info}/entry_points.txt +1 -1
  139. flowfile_core/configs/settings.py +7 -32
  140. flowfile_core/flowfile/FlowfileFlow.py +4 -2
  141. flowfile_core/flowfile/analytics/analytics_processor.py +1 -1
  142. flowfile_core/main.py +4 -1
  143. flowfile_core/schemas/input_schema.py +1 -8
  144. flowfile_frame/__init__.py +0 -1
  145. flowfile_frame/utils.py +0 -139
  146. flowfile-0.3.0.dist-info/METADATA +0 -219
  147. flowfile_frame/__main__.py +0 -12
  148. {flowfile-0.3.0.dist-info → flowfile-0.3.0.2.dist-info}/LICENSE +0 -0
  149. {flowfile-0.3.0.dist-info → flowfile-0.3.0.2.dist-info}/WHEEL +0 -0
flowfile/__init__.py CHANGED
@@ -7,9 +7,16 @@ This package ties together the FlowFile ecosystem components:
7
7
  - flowfile_worker: Computation engine
8
8
  """
9
9
 
10
- __version__ = "0.2.1"
10
+ __version__ = "0.3.1"
11
11
 
12
- # Import the key components from flowfile_frame
12
+ import os
13
+ import logging
14
+
15
+ os.environ['WORKER_PORT'] = "63578"
16
+ os.environ['SINGLE_FILE_MODE'] = "1"
17
+
18
+ from flowfile.web import start_server as start_web_ui
19
+ from flowfile.api import open_graph_in_editor
13
20
  from flowfile_frame.flow_frame import (
14
21
  FlowFrame, read_csv, read_parquet, from_dict, concat
15
22
  )
@@ -18,7 +25,7 @@ from flowfile_frame.expr import (
18
25
  sum, min, max, mean, count, when
19
26
  )
20
27
  from flowfile_frame.group_frame import GroupByFrame
21
- from flowfile_frame.utils import create_flow_graph, open_graph_in_editor
28
+ from flowfile_frame.utils import create_flow_graph
22
29
  from flowfile_frame.selectors import (
23
30
  numeric, float_, integer, string, temporal,
24
31
  datetime, date, time, duration, boolean,
@@ -26,7 +33,6 @@ from flowfile_frame.selectors import (
26
33
  by_dtype, contains, starts_with, ends_with, matches
27
34
  )
28
35
 
29
- # Import Polars data types for convenience
30
36
  from polars.datatypes import (
31
37
  Int8, Int16, Int32, Int64, Int128,
32
38
  UInt8, UInt16, UInt32, UInt64,
@@ -38,7 +44,6 @@ from polars.datatypes import (
38
44
  DataType, DataTypeClass, Field
39
45
  )
40
46
 
41
- # Define what's publicly available from the package
42
47
  __all__ = [
43
48
  # Core FlowFrame classes
44
49
  'FlowFrame', 'GroupByFrame',
@@ -68,4 +73,6 @@ __all__ = [
68
73
  'Date', 'Time', 'Datetime', 'Duration',
69
74
  'Categorical', 'Decimal', 'Enum', 'Unknown',
70
75
  'DataType', 'DataTypeClass', 'Field',
71
- ]
76
+ 'start_web_ui'
77
+ ]
78
+ logging.getLogger("PipelineHandler").setLevel(logging.WARNING)
flowfile/__main__.py CHANGED
@@ -1,24 +1,59 @@
1
- """
2
- Main entry point for the FlowFile package.
3
- """
4
-
1
+ # flowfile/__main__.py
5
2
 
6
3
  def main():
7
4
  """
8
5
  Display information about FlowFile when run directly as a module.
9
6
  """
10
7
  import flowfile
8
+ import argparse
11
9
 
12
- print(f"FlowFile v{flowfile.__version__}")
13
- print("A framework combining visual ETL with a Polars-like API")
14
- print("\nUsage examples:")
15
- print(" import flowfile as ff")
16
- print(" df = ff.read_csv('data.csv')")
17
- print(" result = df.filter(ff.col('value') > 10)")
18
- print(" result.write_csv('output.csv')")
19
- print("\nFor visual ETL:")
20
- print(" ff.open_graph_in_editor(result.to_graph())")
10
+ parser = argparse.ArgumentParser(description="FlowFile: A visual ETL tool with a Polars-like API")
11
+ parser.add_argument("command", nargs="?", choices=["run"], help="Command to execute")
12
+ parser.add_argument("component", nargs="?", choices=["web", "core", "worker"],
13
+ help="Component to run (web, core, or worker)")
14
+ parser.add_argument("--host", default="127.0.0.1", help="Host to bind the server to")
15
+ parser.add_argument("--port", type=int, default=63578, help="Port to bind the server to")
16
+ parser.add_argument("--no-browser", action="store_true", help="Don't open a browser window")
21
17
 
18
+ # Parse arguments
19
+ args = parser.parse_args()
22
20
 
23
- if __name__ == "__main__":
24
- main()
21
+ if args.command == "run" and args.component:
22
+ if args.component == "ui":
23
+ try:
24
+ flowfile.start_web_ui(
25
+ host=args.host,
26
+ port=args.port,
27
+ open_browser=not args.no_browser
28
+ )
29
+ except KeyboardInterrupt:
30
+ print("\nFlowFile service stopped.")
31
+ elif args.component == "core":
32
+ # Only for direct core service usage
33
+ from flowfile_core.main import run as run_core
34
+ run_core(host=args.host, port=args.port)
35
+ elif args.component == "worker":
36
+ # Only for direct worker service usage
37
+ from flowfile_worker.main import run as run_worker
38
+ run_worker(host=args.host, port=args.port)
39
+ else:
40
+ # Default action - show info
41
+ print(f"FlowFile v{flowfile.__version__}")
42
+ print("A framework combining visual ETL with a Polars-like API")
43
+ print("\nUsage:")
44
+ print(" # Start the FlowFile web UI with integrated services")
45
+ print(" flowfile run ui")
46
+ print("")
47
+ print(" # Advanced: Run individual components")
48
+ print(" flowfile run core # Start only the core service")
49
+ print(" flowfile run worker # Start only the worker service")
50
+ print("")
51
+ print(" # Options")
52
+ print(" flowfile run ui --host 0.0.0.0 --port 8080 # Custom host/port")
53
+ print(" flowfile run ui --no-browser # Don't open browser")
54
+ print("")
55
+ print(" # Python API usage examples")
56
+ print(" import flowfile as ff")
57
+ print(" df = ff.read_csv('data.csv')")
58
+ print(" result = df.filter(ff.col('value') > 10)")
59
+ print(" ff.open_graph_in_editor(result)")
flowfile/api.py ADDED
@@ -0,0 +1,383 @@
1
+ # flowfile/api.py
2
+ import uuid
3
+ import time
4
+ import os
5
+ import requests
6
+ import subprocess
7
+ import sys
8
+ import atexit
9
+ import logging
10
+ import webbrowser
11
+ import shutil
12
+ from pathlib import Path
13
+ from typing import Optional, Dict, Any, Union, Tuple, List
14
+ from subprocess import Popen
15
+ from flowfile_core.flowfile.FlowfileFlow import FlowGraph
16
+ from tempfile import TemporaryDirectory
17
+
18
+ # Configuration
19
+ FLOWFILE_HOST: str = os.environ.get("FLOWFILE_HOST", "127.0.0.1")
20
+ FLOWFILE_PORT: int = int(os.environ.get("FLOWFILE_PORT", 63578))
21
+ FLOWFILE_BASE_URL: str = f"http://{FLOWFILE_HOST}:{FLOWFILE_PORT}"
22
+ DEFAULT_MODULE_NAME: str = os.environ.get("FLOWFILE_MODULE_NAME", "flowfile")
23
+ FORCE_POETRY: bool = os.environ.get("FORCE_POETRY", "").lower() in ("true", "1", "yes")
24
+ POETRY_PATH: str = os.environ.get("POETRY_PATH", "poetry")
25
+
26
+ logger: logging.Logger = logging.getLogger(__name__)
27
+
28
+ # Global variable to track the managed server process
29
+ _server_process: Optional[Popen] = None
30
+
31
+
32
+ def is_flowfile_running() -> bool:
33
+ """Check if the Flowfile core API endpoint is responsive."""
34
+ try:
35
+ response: requests.Response = requests.get(f"{FLOWFILE_BASE_URL}/docs", timeout=1)
36
+ return 200 <= response.status_code < 300
37
+ except (requests.ConnectionError, requests.Timeout):
38
+ return False
39
+ except Exception as e:
40
+ logger.error(f"Unexpected error checking Flowfile status: {e}")
41
+ return False
42
+
43
+
44
+ def stop_flowfile_server_process() -> None:
45
+ """Stop the Flowfile server process if it was started by this module."""
46
+ global _server_process
47
+ if _server_process and _server_process.poll() is None:
48
+ logger.info(f"Stopping managed Flowfile server process (PID: {_server_process.pid})...")
49
+ _server_process.terminate()
50
+ try:
51
+ _server_process.wait(timeout=5)
52
+ logger.info("Server process terminated gracefully.")
53
+ except subprocess.TimeoutExpired:
54
+ logger.warning("Server process did not terminate gracefully, killing...")
55
+ _server_process.kill()
56
+ _server_process.wait()
57
+ logger.info("Server process killed.")
58
+ except Exception as e:
59
+ logger.error(f"Error during server process termination: {e}")
60
+ finally:
61
+ _server_process = None
62
+
63
+
64
+ def is_poetry_environment() -> bool:
65
+ """
66
+ Detect if we're running in a Poetry environment by checking:
67
+ 1. If pyproject.toml exists up the directory tree
68
+ 2. If VIRTUAL_ENV points to a poetry environment
69
+ 3. If POETRY_ACTIVE environment variable is set
70
+ """
71
+ # Check if explicitly set via env variable
72
+ if FORCE_POETRY:
73
+ return True
74
+
75
+ # Check if POETRY_ACTIVE is set
76
+ if os.environ.get("POETRY_ACTIVE", "").lower() in ("true", "1", "yes"):
77
+ return True
78
+
79
+ # Check if we're in a poetry virtual environment
80
+ venv_path = os.environ.get("VIRTUAL_ENV", "")
81
+ if venv_path and (
82
+ "poetry" in venv_path.lower() or
83
+ Path(venv_path).joinpath(".poetry-venv").exists()
84
+ ):
85
+ return True
86
+
87
+ # Look for pyproject.toml with poetry section
88
+ cwd = Path.cwd()
89
+ for parent in [cwd, *cwd.parents]:
90
+ pyproject = parent / "pyproject.toml"
91
+ if pyproject.exists():
92
+ with open(pyproject, "r") as f:
93
+ content = f.read()
94
+ if "[tool.poetry]" in content:
95
+ return True
96
+
97
+ return False
98
+
99
+
100
+ def is_command_available(command: str) -> bool:
101
+ """Check if a command is available in the PATH."""
102
+ return shutil.which(command) is not None
103
+
104
+
105
+ def build_server_command(module_name: str) -> List[str]:
106
+ """
107
+ Build the appropriate command to start the server based on environment detection.
108
+ Tries Poetry first if in a Poetry environment, falls back to direct module execution.
109
+ """
110
+ command: List[str] = []
111
+
112
+ # Case 1: Check if we're in a Poetry environment
113
+ if is_poetry_environment():
114
+ logger.info("Poetry environment detected.")
115
+ if is_command_available(POETRY_PATH):
116
+ logger.info(f"Using Poetry to run {module_name}")
117
+ command = [
118
+ POETRY_PATH,
119
+ "run",
120
+ module_name,
121
+ "run",
122
+ "ui",
123
+ "--no-browser",
124
+ f"--port={FLOWFILE_PORT}",
125
+ ]
126
+ return command
127
+ else:
128
+ logger.warning(f"Poetry command not found at '{POETRY_PATH}'. Falling back to Python module.")
129
+
130
+ # Case 2: Try direct module execution
131
+ logger.info(f"Using Python module approach with {module_name}")
132
+ command = [
133
+ sys.executable,
134
+ "-m",
135
+ module_name,
136
+ "run",
137
+ "ui",
138
+ "--no-browser",
139
+ f"--port={FLOWFILE_PORT}",
140
+ ]
141
+
142
+ return command
143
+
144
+
145
+ def start_flowfile_server_process(module_name: str = DEFAULT_MODULE_NAME) -> bool:
146
+ """
147
+ Start the Flowfile server as a background process if it's not already running.
148
+ Automatically detects and uses Poetry if in a Poetry environment.
149
+
150
+ Parameters:
151
+ module_name: The module name to run. Defaults to the value from environment
152
+ variable or "flowfile".
153
+ """
154
+ global _server_process
155
+ if is_flowfile_running():
156
+ return True
157
+
158
+ if _server_process and _server_process.poll() is None:
159
+ logger.warning("Server process object exists but API not responding. Attempting to restart.")
160
+ stop_flowfile_server_process()
161
+
162
+ # Build command automatically based on environment detection
163
+ command = build_server_command(module_name)
164
+ logger.info(f"Starting server with command: {' '.join(command)}")
165
+
166
+ try:
167
+ _server_process = Popen(
168
+ command,
169
+ stdout=subprocess.DEVNULL,
170
+ stderr=subprocess.PIPE,
171
+ )
172
+ logger.info(f"Started server process with PID: {_server_process.pid}")
173
+
174
+ atexit.register(stop_flowfile_server_process)
175
+
176
+ logger.info("Waiting for server to initialize...")
177
+
178
+ for i in range(10):
179
+ time.sleep(1)
180
+ if is_flowfile_running():
181
+ logger.info("Server started successfully.")
182
+ return True
183
+ else:
184
+ logger.error("Failed to start server: API did not become responsive.")
185
+ if _server_process and _server_process.stderr:
186
+ try:
187
+ stderr_output: str = _server_process.stderr.read().decode(errors='ignore')
188
+ logger.error(f"Server process stderr:\n{stderr_output[:1000]}...")
189
+ except Exception as read_err:
190
+ logger.error(f"Could not read stderr from server process: {read_err}")
191
+ stop_flowfile_server_process()
192
+ return False
193
+ else:
194
+ stop_flowfile_server_process()
195
+ return False
196
+
197
+ except FileNotFoundError:
198
+ logger.error(f"Error: Could not execute command: '{' '.join(command)}'.")
199
+ logger.error(f"Ensure '{module_name}' is installed correctly.")
200
+ _server_process = None
201
+ return False
202
+ except Exception as e:
203
+ logger.error(f"An unexpected error occurred while starting the server process: {e}")
204
+ if _server_process and _server_process.stderr:
205
+ try:
206
+ stderr_output = _server_process.stderr.read().decode(errors='ignore')
207
+ logger.error(f"Server process stderr:\n{stderr_output[:1000]}...")
208
+ except Exception as read_err:
209
+ logger.error(f"Could not read stderr from server process: {read_err}")
210
+ stop_flowfile_server_process()
211
+ _server_process = None
212
+ return False
213
+
214
+
215
+ def get_auth_token() -> Optional[str]:
216
+ """Get an authentication token from the Flowfile API."""
217
+ try:
218
+ response: requests.Response = requests.post(
219
+ f"{FLOWFILE_BASE_URL}/auth/token",
220
+ json={},
221
+ timeout=5
222
+ )
223
+ response.raise_for_status()
224
+ token_data: Dict[str, Any] = response.json()
225
+ access_token: Optional[str] = token_data.get("access_token")
226
+ if not access_token:
227
+ logger.error("Auth token endpoint succeeded but 'access_token' was missing in response.")
228
+ return None
229
+ return access_token
230
+
231
+ except requests.exceptions.RequestException as e:
232
+ logger.error(f"Failed to get auth token: {e}")
233
+ return None
234
+ except Exception as e:
235
+ logger.error(f"An unexpected error occurred getting auth token: {e}")
236
+ return None
237
+
238
+
239
+ def import_flow_to_editor(flow_path: Path, auth_token: str) -> Optional[int]:
240
+ """Import the flow into the Flowfile editor using the API endpoint."""
241
+ if not flow_path.is_file():
242
+ logger.error(f"Flow file not found: {flow_path}")
243
+ return None
244
+ if not auth_token:
245
+ logger.error("Cannot import flow without auth token.")
246
+ return None
247
+
248
+ try:
249
+ headers: Dict[str, str] = {"Authorization": f"Bearer {auth_token}"}
250
+ params: Dict[str, str] = {"flow_path": str(flow_path)}
251
+
252
+ response: requests.Response = requests.get(
253
+ f"{FLOWFILE_BASE_URL}/import_flow/",
254
+ params=params,
255
+ headers=headers,
256
+ timeout=10
257
+ )
258
+ response.raise_for_status()
259
+
260
+ flow_id_data: Union[int, Dict[str, Any], Any] = response.json()
261
+ flow_id: Optional[int] = None
262
+
263
+ if isinstance(flow_id_data, int):
264
+ flow_id = flow_id_data
265
+ elif isinstance(flow_id_data, dict) and "flow_id" in flow_id_data:
266
+ flow_id = int(flow_id_data["flow_id"])
267
+ else:
268
+ logger.error(f"Unexpected response format from import_flow endpoint: {flow_id_data}")
269
+ return None
270
+
271
+ logger.info(f"Flow '{flow_path.name}' imported successfully with ID: {flow_id}")
272
+ return flow_id
273
+
274
+ except requests.exceptions.RequestException as e:
275
+ logger.error(f"Failed to import flow: {e}")
276
+ if e.response is not None:
277
+ logger.error(f"Server response: {e.response.status_code} - {e.response.text[:500]}")
278
+ return None
279
+ except Exception as e:
280
+ logger.error(f"An unexpected error occurred importing flow: {e}")
281
+ return None
282
+
283
+
284
+ def _save_flow_to_location(
285
+ flow_graph: FlowGraph, storage_location: Optional[str]
286
+ ) -> Tuple[Optional[Path], Optional[TemporaryDirectory]]:
287
+ """Handles graph saving, path resolution, and temporary directory creation."""
288
+ temp_dir_obj: Optional[TemporaryDirectory] = None
289
+ flow_file_path: Path
290
+ try:
291
+ if storage_location:
292
+ flow_file_path = Path(storage_location).resolve()
293
+ flow_file_path.parent.mkdir(parents=True, exist_ok=True)
294
+ else:
295
+ temp_dir_obj = TemporaryDirectory(prefix="flowfile_graph_")
296
+ flow_file_path = Path(temp_dir_obj.name) / f"temp_flow_{uuid.uuid4().hex[:8]}.flowfile"
297
+
298
+ logger.info(f"Applying layout and saving flow to: {flow_file_path}")
299
+ flow_graph.apply_layout()
300
+ flow_graph.save_flow(str(flow_file_path))
301
+ logger.info("Flow saved successfully.")
302
+ return flow_file_path, temp_dir_obj
303
+ except Exception as e:
304
+ logger.error(f"Failed to save flow graph to location '{storage_location}': {e}", exc_info=True)
305
+ if temp_dir_obj:
306
+ try:
307
+ temp_dir_obj.cleanup()
308
+ logger.info(f"Cleaned up temp dir {temp_dir_obj.name} after save failure.")
309
+ except Exception as cleanup_err:
310
+ logger.error(f"Error during immediate cleanup of temp dir: {cleanup_err}")
311
+ return None, None
312
+
313
+
314
+ def _open_flow_in_browser(flow_id: int) -> None:
315
+ """Opens the specified flow ID in a browser tab if in unified mode."""
316
+ if os.environ.get("FLOWFILE_MODE") == "electron":
317
+ flow_url = f"http://{FLOWFILE_HOST}:{FLOWFILE_PORT}/ui/flow/{flow_id}"
318
+ logger.info(f"Unified mode detected. Opening imported flow in browser: {flow_url}")
319
+ try:
320
+ time.sleep(0.5)
321
+ webbrowser.open_new_tab(flow_url)
322
+ except Exception as wb_err:
323
+ logger.warning(f"Could not automatically open browser tab: {wb_err}")
324
+ else:
325
+ logger.info("Not in unified mode ('electron'), browser will not be opened automatically.")
326
+
327
+
328
+ def _cleanup_temporary_storage(temp_dir_obj: Optional[TemporaryDirectory]) -> None:
329
+ """Safely cleans up the temporary directory if one was created."""
330
+ if temp_dir_obj:
331
+ try:
332
+ temp_dir_obj.cleanup()
333
+ logger.info(f"Cleaned up temporary directory: {temp_dir_obj.name}")
334
+ except Exception as e:
335
+ logger.error(f"Error cleaning up temporary directory {temp_dir_obj.name}: {e}")
336
+
337
+
338
+ def open_graph_in_editor(flow_graph: FlowGraph, storage_location: Optional[str] = None,
339
+ module_name: str = DEFAULT_MODULE_NAME) -> bool:
340
+ """
341
+ Save the ETL graph, ensure the Flowfile server is running (starting it
342
+ if necessary), import the graph via API, and open it in a new browser
343
+ tab if running in unified mode.
344
+
345
+ Parameters:
346
+ flow_graph: The FlowGraph object to save and open.
347
+ storage_location: Optional path to save the .flowfile. If None,
348
+ a temporary file is used.
349
+ module_name: The module name to run if server needs to be started.
350
+ Use your Poetry package name if not using "flowfile".
351
+
352
+ Returns:
353
+ True if the graph was successfully imported, False otherwise.
354
+ """
355
+ temp_dir_obj: Optional[TemporaryDirectory] = None
356
+ try:
357
+ original_execution_settings = flow_graph.flow_settings.model_copy()
358
+ flow_graph.flow_settings.execution_location = "auto"
359
+ flow_graph.flow_settings.execution_mode = "Development"
360
+ flow_file_path, temp_dir_obj = _save_flow_to_location(flow_graph, storage_location)
361
+ if not flow_file_path:
362
+ return False
363
+ flow_graph.flow_settings = original_execution_settings
364
+ if not start_flowfile_server_process(module_name):
365
+ return False
366
+
367
+ auth_token = get_auth_token()
368
+ if not auth_token:
369
+ return False
370
+
371
+ flow_id = import_flow_to_editor(flow_file_path, auth_token)
372
+
373
+ if flow_id is not None:
374
+ _open_flow_in_browser(flow_id)
375
+ return True
376
+ else:
377
+ return False
378
+
379
+ except Exception as e:
380
+ logger.error(f"An unexpected error occurred in open_graph_in_editor: {e}", exc_info=True)
381
+ return False
382
+ finally:
383
+ _cleanup_temporary_storage(temp_dir_obj)
flowfile/readme.md ADDED
@@ -0,0 +1,130 @@
1
+ # Flowfile Web UI Documentation
2
+
3
+ ## Overview
4
+
5
+ Flowfile now supports a web-based user interface that can be launched directly from the pip-installed package. This enhancement allows users to quickly get started with the visual ETL tool without needing to install the desktop application, set up Docker, or manually configure the services.
6
+
7
+ ## Key Features
8
+
9
+ - **Integrated Web UI**: Launch the Flowfile interface directly in your browser
10
+ - **Unified Service**: Combined API that serves both the web UI and processes worker operations
11
+ - **Easy Installation**: Simple pip installation and startup process
12
+ - **Visual ETL**: Access to all the visual ETL capabilities through a web interface
13
+
14
+ ## Installation
15
+
16
+ Install Flowfile from PyPI using pip:
17
+
18
+ ```bash
19
+ pip install Flowfile
20
+ ```
21
+
22
+ ## Starting the Web UI
23
+
24
+ You can start the Flowfile web UI using either the Python module or the command-line interface:
25
+
26
+ ### Using the Command-Line Interface
27
+
28
+ ```bash
29
+ # Start the web UI with default settings
30
+ flowfile run ui
31
+
32
+ # Customize host and port
33
+ flowfile run ui --host 0.0.0.0 --port 8080
34
+
35
+ # Start without automatically opening a browser window
36
+ flowfile run ui --no-browser
37
+ ```
38
+
39
+ ### Using Python
40
+
41
+ ```python
42
+ import flowfile
43
+
44
+ # Start the web UI with default settings
45
+ flowfile.start_web_ui()
46
+
47
+ # Customize host, port, and browser launch
48
+ flowfile.start_web_ui(host="0.0.0.0", port=8080, open_browser=False)
49
+ ```
50
+
51
+ ## Architecture Overview
52
+
53
+ The web UI functionality combines multiple components:
54
+
55
+ 1. **Core Service**: The main ETL engine (flowfile_core) that processes data transformations
56
+ 2. **Worker Service**: Handles computation and caching of data operations (flowfile_worker)
57
+ 3. **Web UI**: A Vue.js frontend that provides the visual interface
58
+
59
+ When you start the web UI, all these services are launched together in a unified mode, making it simple to get started without configuration.
60
+
61
+ ## Using the Web UI with FlowFrame API
62
+
63
+ You can create data pipelines programmatically with the FlowFrame API and then visualize them in the web UI:
64
+
65
+ ```python
66
+ import flowfile as ff
67
+ from flowfile import open_graph_in_editor
68
+
69
+ # Create a data pipeline
70
+ df = ff.from_dict({
71
+ "id": [1, 2, 3, 4, 5],
72
+ "category": ["A", "B", "A", "C", "B"],
73
+ "value": [100, 200, 150, 300, 250]
74
+ })
75
+
76
+ # Process the data
77
+ result = df.filter(ff.col("value") > 150).with_columns([
78
+ (ff.col("value") * 2).alias("double_value")
79
+ ])
80
+
81
+ # Open the graph in the web UI (starts the server if it's not running)
82
+ open_graph_in_editor(result.flow_graph)
83
+ ```
84
+
85
+ The `open_graph_in_editor` function automatically:
86
+ 1. Saves the flow graph to a temporary file
87
+ 2. Starts the Flowfile server if it's not already running
88
+ 3. Imports the flow into the editor
89
+ 4. Opens a browser tab with the imported flow
90
+
91
+ ## Advanced Server Configuration
92
+
93
+ For advanced users who need to customize the server behavior:
94
+
95
+ ### Environment Variables
96
+
97
+ - `FLOWFILE_HOST`: Host to bind the server to (default: "127.0.0.1")
98
+ - `FLOWFILE_PORT`: Port to bind the server to (default: 63578)
99
+ - `FLOWFILE_MODE`: Set to "electron" to enable browser auto-opening behavior
100
+ - `WORKER_URL`: URL for the worker service
101
+ - `SINGLE_FILE_MODE`: Set to "1" to run in unified mode with worker functionality
102
+ - `FLOWFILE_MODULE_NAME`: Module name to run (default: "flowfile")
103
+
104
+ ### Running Individual Components
105
+
106
+ For development or specialized deployments, you can run the components separately:
107
+
108
+ ```bash
109
+ # Run only the core service
110
+ flowfile run core --host 0.0.0.0 --port 8080
111
+
112
+ # Run only the worker service
113
+ flowfile run worker --host 0.0.0.0 --port 8081
114
+ ```
115
+
116
+ ## Troubleshooting
117
+
118
+ - If the web UI doesn't open automatically, manually navigate to http://localhost:63578/ui
119
+ - If you encounter connection issues, check if the port is already in use
120
+ - Look for server logs in the terminal where you started the service for error messages
121
+ - For issues with the API, navigate to http://localhost:63578/docs to verify the API is running
122
+
123
+ ## Next Steps
124
+
125
+ Once you're familiar with the web UI, you might want to explore:
126
+
127
+ 1. The desktop application for a more native experience
128
+ 2. Docker deployment for production environments
129
+ 3. Advanced ETL operations using the FlowFrame API
130
+ 4. Custom node development for specialized transformations