pipeline-eds 0.2.14__py3-none-any.whl → 0.2.16__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.
- pipeline/api/eds.py +52 -6
- pipeline/cli.py +36 -17
- pipeline/env.py +2 -2
- pipeline/environment.py +2 -0
- pipeline/gui_fastapi_plotly_live.py +31 -5
- pipeline/gui_mpl_live.py +41 -27
- pipeline/gui_plotly_static.py +43 -0
- pipeline/workspace_manager.py +78 -18
- {pipeline_eds-0.2.14.dist-info → pipeline_eds-0.2.16.dist-info}/METADATA +3 -2
- {pipeline_eds-0.2.14.dist-info → pipeline_eds-0.2.16.dist-info}/RECORD +13 -12
- {pipeline_eds-0.2.14.dist-info → pipeline_eds-0.2.16.dist-info}/LICENSE +0 -0
- {pipeline_eds-0.2.14.dist-info → pipeline_eds-0.2.16.dist-info}/WHEEL +0 -0
- {pipeline_eds-0.2.14.dist-info → pipeline_eds-0.2.16.dist-info}/entry_points.txt +0 -0
pipeline/api/eds.py
CHANGED
@@ -165,7 +165,7 @@ class EdsClient:
|
|
165
165
|
|
166
166
|
@staticmethod
|
167
167
|
#def create_tabular_request(session: object, api_url: str, starttime: int, endtime: int, points: list):
|
168
|
-
def
|
168
|
+
def create_tabular_request_(session, api_url, starttime, endtime, points, step_seconds = 300):
|
169
169
|
|
170
170
|
data = {
|
171
171
|
'period': {
|
@@ -189,6 +189,51 @@ class EdsClient:
|
|
189
189
|
response = session.post(f"{api_url}/trend/tabular", json=data, verify=False)
|
190
190
|
#print(f"response = {response}")
|
191
191
|
|
192
|
+
@staticmethod
|
193
|
+
def create_tabular_request(session, api_url, starttime, endtime, points, step_seconds=300):
|
194
|
+
"""
|
195
|
+
Submit a tabular trend request. Returns request id on success, or None if failed.
|
196
|
+
"""
|
197
|
+
|
198
|
+
data = {
|
199
|
+
"period": {
|
200
|
+
"from": starttime,
|
201
|
+
"till": endtime,
|
202
|
+
},
|
203
|
+
"step": step_seconds,
|
204
|
+
"items": [
|
205
|
+
{
|
206
|
+
"pointId": {"iess": p},
|
207
|
+
"shadePriority": "DEFAULT",
|
208
|
+
"function": "AVG",
|
209
|
+
}
|
210
|
+
for p in points
|
211
|
+
],
|
212
|
+
}
|
213
|
+
|
214
|
+
try:
|
215
|
+
res = session.post(f"{api_url}/trend/tabular", json=data, verify=False)
|
216
|
+
except Exception as e:
|
217
|
+
logger.error(f"Request failed to {api_url}/trend/tabular: {e}")
|
218
|
+
return None
|
219
|
+
|
220
|
+
if res.status_code != 200:
|
221
|
+
logger.error(f"Bad status {res.status_code} from server: {res.text}")
|
222
|
+
return None
|
223
|
+
|
224
|
+
try:
|
225
|
+
payload = res.json()
|
226
|
+
except Exception:
|
227
|
+
logger.error(f"Non-JSON response: {res.text}")
|
228
|
+
return None
|
229
|
+
|
230
|
+
req_id = payload.get("id")
|
231
|
+
if not req_id:
|
232
|
+
logger.error(f"No request id in response: {payload}")
|
233
|
+
return None
|
234
|
+
|
235
|
+
return req_id
|
236
|
+
|
192
237
|
@staticmethod
|
193
238
|
def wait_for_request_execution_session(session, api_url, req_id):
|
194
239
|
st = time.time()
|
@@ -726,7 +771,7 @@ def demo_eds_webplot_point_live():
|
|
726
771
|
gui_fastapi_plotly_live.run_gui(data_buffer)
|
727
772
|
|
728
773
|
@log_function_call(level=logging.DEBUG)
|
729
|
-
def load_historic_data(session, iess_list, starttime, endtime):
|
774
|
+
def load_historic_data(session, iess_list, starttime, endtime, step_seconds):
|
730
775
|
|
731
776
|
|
732
777
|
starttime = TimeManager(starttime).as_unix()
|
@@ -734,11 +779,13 @@ def load_historic_data(session, iess_list, starttime, endtime):
|
|
734
779
|
logger.info(f"starttime = {starttime}")
|
735
780
|
logger.info(f"endtime = {endtime}")
|
736
781
|
|
737
|
-
step_seconds = helpers.nice_step(endtime-starttime)
|
738
782
|
|
739
783
|
point_list = iess_list
|
740
784
|
api_url = str(session.base_url)
|
741
785
|
request_id = EdsClient.create_tabular_request(session, api_url, starttime, endtime, points=point_list, step_seconds=step_seconds)
|
786
|
+
if not request_id:
|
787
|
+
logger.warning(f"Could not create tabular request for points: {point_list}")
|
788
|
+
return [] # or None, depending on how you want the CLI to behave
|
742
789
|
EdsClient.wait_for_request_execution_session(session, api_url, request_id)
|
743
790
|
results = EdsClient.get_tabular_trend(session, request_id, point_list)
|
744
791
|
logger.debug(f"len(results) = {len(results)}")
|
@@ -951,10 +998,9 @@ if __name__ == "__main__":
|
|
951
998
|
demo_eds_save_graphics_export()
|
952
999
|
elif cmd == "license":
|
953
1000
|
demo_eds_print_license()
|
954
|
-
elif cmd == "access-workspace"
|
1001
|
+
elif cmd == "access-workspace":
|
955
1002
|
if platform.system().lower() == "windows":
|
956
|
-
#
|
957
|
-
#command = ["Open-FileBrowser", WorkspaceManager.get_cwd()]
|
1003
|
+
# at this level it is correct but the get_cwd() command only knows the default workspace.
|
958
1004
|
command = ["explorer", str(WorkspaceManager.get_cwd())]
|
959
1005
|
subprocess.call(command)
|
960
1006
|
else:
|
pipeline/cli.py
CHANGED
@@ -17,7 +17,7 @@ from importlib.metadata import version, PackageNotFoundError
|
|
17
17
|
|
18
18
|
from pipeline.env import SecretConfig
|
19
19
|
#from pipeline.helpers import setup_logging
|
20
|
-
from pipeline.workspace_manager import WorkspaceManager
|
20
|
+
#from pipeline.workspace_manager import WorkspaceManager
|
21
21
|
|
22
22
|
### Versioning
|
23
23
|
CLI_APP_NAME = "pipeline"
|
@@ -30,13 +30,13 @@ def print_version(value: bool):
|
|
30
30
|
raise typer.Exit()
|
31
31
|
try:
|
32
32
|
PIPELINE_VERSION = version(CLI_APP_NAME)
|
33
|
-
__version__ = version(
|
33
|
+
__version__ = version(CLI_APP_NAME)
|
34
34
|
except PackageNotFoundError:
|
35
35
|
PIPELINE_VERSION = "unknown"
|
36
36
|
|
37
37
|
try:
|
38
38
|
from importlib.metadata import version
|
39
|
-
__version__ = version(
|
39
|
+
__version__ = version(CLI_APP_NAME)
|
40
40
|
except PackageNotFoundError:
|
41
41
|
# fallback if running from source
|
42
42
|
try:
|
@@ -71,6 +71,7 @@ def run(
|
|
71
71
|
Import and run a workspace's main() function.
|
72
72
|
"""
|
73
73
|
# Determine workspace name
|
74
|
+
from pipeline.workspace_manager import WorkspaceManager
|
74
75
|
if workspace is None:
|
75
76
|
workspace = WorkspaceManager.identify_default_workspace_name()
|
76
77
|
wm = WorkspaceManager(workspace)
|
@@ -103,7 +104,9 @@ def trend(
|
|
103
104
|
starttime: str = typer.Option(None, "--start", "-s", help="Index from 'mulch order' to choose scaffold source."),
|
104
105
|
endtime: str = typer.Option(None, "--end", "-end", help="Reference a known template for workspace organization."),
|
105
106
|
zd: str = typer.Option('Maxson', "--zd", "-z", help = "Define the EDS ZD from your secrets file. This must correlate with your idcs point selection(s)."),
|
106
|
-
|
107
|
+
workspacename: str = typer.Option(None,"--workspace","-w", help = "Provide the name of the workspace you want to use, for the secrets.yaml credentials and for the timezone config. If a start time is not provided, the workspace queries can checked for the most recent successful timestamp. "),
|
108
|
+
print_csv: bool = typer.Option(False,"--print-csv","-p",help = "Print the CSV style for pasting into Excel."),
|
109
|
+
step_seconds: int = typer.Option(None, "--step-seconds", help="You can explicitly provide the delta between datapoints. If not, ~400 data points will be used, based on the nice_step() function.")
|
107
110
|
):
|
108
111
|
"""
|
109
112
|
Show a curve for a sensor over time.
|
@@ -113,16 +116,16 @@ def trend(
|
|
113
116
|
from pipeline.api.eds import EdsClient, load_historic_data
|
114
117
|
from pipeline import helpers
|
115
118
|
from pipeline.plotbuffer import PlotBuffer
|
116
|
-
from pipeline import gui_fastapi_plotly_live
|
117
119
|
from pipeline import environment
|
118
120
|
from pipeline.workspace_manager import WorkspaceManager
|
119
|
-
|
121
|
+
workspaces_dir = WorkspaceManager.ensure_appdata_workspaces_dir()
|
120
122
|
|
121
123
|
# must set up %appdata for pip/x installation. Use mulch or yeoman for this. And have a secrets filler.
|
122
|
-
if
|
123
|
-
WorkspaceManager.identify_default_workspace_name()
|
124
|
-
wm = WorkspaceManager(
|
125
|
-
|
124
|
+
if workspacename is None:
|
125
|
+
workspacename = WorkspaceManager.identify_default_workspace_name()
|
126
|
+
wm = WorkspaceManager(workspacename)
|
127
|
+
secrets_file_path = wm.get_secrets_file_path()
|
128
|
+
secrets_dict = SecretConfig.load_config(secrets_file_path)
|
126
129
|
|
127
130
|
if zd.lower() == "stiles":
|
128
131
|
zd = "WWTF"
|
@@ -157,22 +160,38 @@ def trend(
|
|
157
160
|
dt_finish = pendulum.parse(endtime, strict=False)
|
158
161
|
|
159
162
|
# Should automatically choose time step granularity based on time length; map
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
+
if step_seconds is None:
|
164
|
+
step_seconds = helpers.nice_step(TimeManager(dt_finish).as_unix()-TimeManager(dt_start.as_unix())) # TimeManager(starttime).as_unix()
|
165
|
+
results = load_historic_data(session, iess_list, dt_start, dt_finish, step_seconds)
|
166
|
+
if not results:
|
167
|
+
return
|
168
|
+
|
163
169
|
data_buffer = PlotBuffer()
|
164
170
|
for idx, rows in enumerate(results):
|
165
171
|
for row in rows:
|
166
|
-
label = f"
|
172
|
+
#label = f"({row.get('units')})"
|
173
|
+
label = iess_list[0]
|
167
174
|
ts = helpers.iso(row.get("ts"))
|
168
175
|
av = row.get("value")
|
176
|
+
#print(f"{round(av,2)}")
|
169
177
|
data_buffer.append(label, ts, av) # needs to be adapted for multiple iess sensor results
|
170
|
-
|
178
|
+
#print(f"data_buffer = {data_buffer}")
|
179
|
+
#print(f"data_buffer.get_all() = {data_buffer.get_all()}")
|
171
180
|
if not environment.matplotlib_enabled():
|
172
|
-
|
181
|
+
from pipeline import gui_plotly_static
|
182
|
+
#gui_fastapi_plotly_live.run_gui(data_buffer)
|
183
|
+
gui_plotly_static.show_static(data_buffer)
|
173
184
|
else:
|
174
185
|
from pipeline import gui_mpl_live
|
175
|
-
gui_mpl_live.run_gui(data_buffer)
|
186
|
+
#gui_mpl_live.run_gui(data_buffer)
|
187
|
+
gui_mpl_live.show_static(data_buffer)
|
188
|
+
|
189
|
+
if print_csv:
|
190
|
+
print(f"Time,\\{iess_list[0]}\\,")
|
191
|
+
for idx, rows in enumerate(results):
|
192
|
+
for row in rows:
|
193
|
+
print(f"{helpers.iso(row.get('ts'))},{row.get('value')},")
|
194
|
+
|
176
195
|
|
177
196
|
@app.command()
|
178
197
|
def list_workspaces():
|
pipeline/env.py
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
#env.__main__.py
|
2
2
|
|
3
3
|
import yaml
|
4
|
-
from pipeline.workspace_manager import WorkspaceManager
|
5
|
-
|
6
4
|
'''
|
7
5
|
migrate this to ConfigurationManager
|
8
6
|
'''
|
@@ -49,6 +47,8 @@ def demo_secrets():
|
|
49
47
|
caed defaut_workspace.toml - Clayton Bennett 26 April 2025.
|
50
48
|
However this call can also be made if another project is made the active project.
|
51
49
|
"""
|
50
|
+
from pipeline.workspace_manager import WorkspaceManager
|
51
|
+
|
52
52
|
workspace_name = WorkspaceManager.identify_default_workspace_name()
|
53
53
|
workspace_manager = WorkspaceManager(workspace_name)
|
54
54
|
config = SecretConfig.load_config(secrets_file_path = workspace_manager.get_secrets_file_path())
|
pipeline/environment.py
CHANGED
@@ -12,11 +12,13 @@ def vercel():
|
|
12
12
|
return False # hard code this
|
13
13
|
|
14
14
|
def matplotlib_enabled():
|
15
|
+
#print(f"is_termux() = {is_termux()}")
|
15
16
|
if is_termux():
|
16
17
|
return False
|
17
18
|
else:
|
18
19
|
try:
|
19
20
|
import matplotlib
|
21
|
+
return True
|
20
22
|
except ImportError:
|
21
23
|
return False
|
22
24
|
|
@@ -57,12 +57,32 @@ HTML_TEMPLATE = """
|
|
57
57
|
async def index():
|
58
58
|
return HTML_TEMPLATE
|
59
59
|
|
60
|
+
|
60
61
|
@app.get("/data", response_class=JSONResponse)
|
61
62
|
async def get_data():
|
63
|
+
if plot_buffer is None:
|
64
|
+
print("plot_buffer is None")
|
65
|
+
return {}
|
62
66
|
with buffer_lock:
|
63
|
-
data = plot_buffer.get_all()
|
64
|
-
|
65
|
-
|
67
|
+
data = plot_buffer.get_all()
|
68
|
+
print("Data in buffer:", data) # <-- DEBUG
|
69
|
+
fixed_data = {}
|
70
|
+
for label, series in data.items():
|
71
|
+
fixed_data[label] = {
|
72
|
+
"x": [ts + "Z" if not ts.endswith("Z") else ts for ts in series["x"]],
|
73
|
+
"y": series["y"]
|
74
|
+
}
|
75
|
+
return fixed_data
|
76
|
+
"""
|
77
|
+
@app.get("/data", response_class=JSONResponse)
|
78
|
+
async def get_data():
|
79
|
+
return {
|
80
|
+
"Test Series": {
|
81
|
+
"x": ["2025-09-05T15:00:00Z", "2025-09-05T15:05:00Z", "2025-09-05T15:10:00Z"],
|
82
|
+
"y": [1, 3, 2]
|
83
|
+
}
|
84
|
+
}
|
85
|
+
"""
|
66
86
|
def open_browser(port):
|
67
87
|
time.sleep(1) # Give server a moment to start
|
68
88
|
## Open in a new Chrome window (if installed)
|
@@ -71,8 +91,14 @@ def open_browser(port):
|
|
71
91
|
|
72
92
|
webbrowser.open(f"http://127.0.0.1:{port}")
|
73
93
|
|
94
|
+
#def run_gui(buffer, port=8000):
|
95
|
+
# global plot_buffer
|
96
|
+
# plot_buffer = buffer
|
97
|
+
# threading.Thread(target=open_browser, args=(port,), daemon=True).start()
|
98
|
+
# uvicorn.run("src.pipeline.gui_fastapi_plotly_live:app", host="127.0.0.1", port=port, log_level="info", reload=False)
|
99
|
+
|
74
100
|
def run_gui(buffer, port=8000):
|
75
101
|
global plot_buffer
|
76
|
-
plot_buffer = buffer
|
102
|
+
plot_buffer = buffer # set the buffer in this process
|
77
103
|
threading.Thread(target=open_browser, args=(port,), daemon=True).start()
|
78
|
-
uvicorn.run(
|
104
|
+
uvicorn.run(app, host="127.0.0.1", port=port, log_level="info", reload=False) # <- reload=False
|
pipeline/gui_mpl_live.py
CHANGED
@@ -3,6 +3,8 @@ import time
|
|
3
3
|
import logging
|
4
4
|
import matplotlib.pyplot as plt
|
5
5
|
import matplotlib.animation as animation
|
6
|
+
import matplotlib.dates as mdates
|
7
|
+
from datetime import datetime, timedelta
|
6
8
|
from pipeline import helpers
|
7
9
|
from pipeline.plotbuffer import PlotBuffer # Adjust import path as needed
|
8
10
|
from pipeline.time_manager import TimeManager
|
@@ -11,28 +13,6 @@ logger = logging.getLogger(__name__)
|
|
11
13
|
|
12
14
|
PADDING_RATIO = 0.25
|
13
15
|
|
14
|
-
def compute_padded_bounds(data):
|
15
|
-
all_x_vals = []
|
16
|
-
all_y_vals = []
|
17
|
-
|
18
|
-
for series in data.values():
|
19
|
-
all_x_vals.extend(series["x"])
|
20
|
-
all_y_vals.extend(series["y"])
|
21
|
-
|
22
|
-
if not all_x_vals or not all_y_vals:
|
23
|
-
return (0, 1), (0, 1)
|
24
|
-
|
25
|
-
x_min, x_max = min(all_x_vals), max(all_x_vals)
|
26
|
-
y_min, y_max = min(all_y_vals), max(all_y_vals)
|
27
|
-
|
28
|
-
x_pad = max((x_max - x_min) * PADDING_RATIO, 1.0)
|
29
|
-
y_pad = max((y_max - y_min) * PADDING_RATIO, 1.0)
|
30
|
-
|
31
|
-
padded_x = (x_min - x_pad, x_max + x_pad)
|
32
|
-
padded_y = (y_min - y_pad, y_max + y_pad)
|
33
|
-
|
34
|
-
return padded_x, padded_y
|
35
|
-
|
36
16
|
def run_gui(buffer: PlotBuffer, update_interval_ms=1000):
|
37
17
|
"""
|
38
18
|
Runs a matplotlib live updating plot based on the PlotBuffer content.
|
@@ -45,6 +25,11 @@ def run_gui(buffer: PlotBuffer, update_interval_ms=1000):
|
|
45
25
|
ax.set_title("Live Pipeline Data")
|
46
26
|
ax.set_xlabel("Time")
|
47
27
|
ax.set_ylabel("Value")
|
28
|
+
# Auto-locate ticks and auto-format dates
|
29
|
+
locator = mdates.AutoDateLocator()
|
30
|
+
formatter = mdates.AutoDateFormatter(locator)
|
31
|
+
ax.xaxis.set_major_locator(locator)
|
32
|
+
ax.xaxis.set_major_formatter(formatter)
|
48
33
|
|
49
34
|
lines = {}
|
50
35
|
legend_labels = []
|
@@ -80,11 +65,6 @@ def run_gui(buffer: PlotBuffer, update_interval_ms=1000):
|
|
80
65
|
else:
|
81
66
|
lines[label].set_data(x_vals, y_vals)
|
82
67
|
|
83
|
-
# Adjust axes limits with padding
|
84
|
-
padded_x, padded_y = compute_padded_bounds(data)
|
85
|
-
ax.set_xlim(padded_x)
|
86
|
-
ax.set_ylim(padded_y)
|
87
|
-
|
88
68
|
# Format x-axis ticks as human readable time strings
|
89
69
|
|
90
70
|
# Tick positions are x values at those indices
|
@@ -111,3 +91,37 @@ def run_gui(buffer: PlotBuffer, update_interval_ms=1000):
|
|
111
91
|
plt.tight_layout()
|
112
92
|
plt.show()
|
113
93
|
|
94
|
+
def show_static(buffer: PlotBuffer):
|
95
|
+
"""
|
96
|
+
Show a static matplotlib plot of the current PlotBuffer contents,
|
97
|
+
with automatic date formatting based on time span.
|
98
|
+
"""
|
99
|
+
plt.style.use('ggplot')
|
100
|
+
fig, ax = plt.subplots(figsize=(10, 6))
|
101
|
+
ax.set_title("Static Pipeline Data")
|
102
|
+
ax.set_xlabel("Time")
|
103
|
+
ax.set_ylabel("Value")
|
104
|
+
|
105
|
+
data = buffer.get_all()
|
106
|
+
if not data:
|
107
|
+
ax.text(0.5, 0.5, "No data to display", ha='center', va='center')
|
108
|
+
plt.show()
|
109
|
+
return
|
110
|
+
|
111
|
+
for label, series in data.items():
|
112
|
+
# Convert strings to datetime objects for better handling
|
113
|
+
x_vals = [TimeManager(ts).as_datetime() for ts in series["x"]]
|
114
|
+
y_vals = series["y"]
|
115
|
+
|
116
|
+
ax.plot(x_vals, y_vals, marker='o', linestyle='-', label=label)
|
117
|
+
|
118
|
+
# Let matplotlib auto-locate ticks and auto-format
|
119
|
+
locator = mdates.AutoDateLocator()
|
120
|
+
formatter = mdates.AutoDateFormatter(locator)
|
121
|
+
ax.xaxis.set_major_locator(locator)
|
122
|
+
ax.xaxis.set_major_formatter(formatter)
|
123
|
+
|
124
|
+
plt.setp(ax.get_xticklabels(), rotation=45, ha='right')
|
125
|
+
ax.legend()
|
126
|
+
plt.tight_layout()
|
127
|
+
plt.show()
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# src/pipeline/gui_plotly_static.py
|
2
|
+
|
3
|
+
import plotly.graph_objs as go
|
4
|
+
import plotly.offline as pyo
|
5
|
+
import webbrowser
|
6
|
+
import tempfile
|
7
|
+
from threading import Lock
|
8
|
+
|
9
|
+
buffer_lock = Lock() # Optional, if you want thread safety
|
10
|
+
|
11
|
+
def show_static(plot_buffer):
|
12
|
+
"""
|
13
|
+
Renders the current contents of plot_buffer as a static HTML plot.
|
14
|
+
Does not listen for updates.
|
15
|
+
"""
|
16
|
+
if plot_buffer is None:
|
17
|
+
print("plot_buffer is None")
|
18
|
+
return
|
19
|
+
|
20
|
+
with buffer_lock:
|
21
|
+
data = plot_buffer.get_all()
|
22
|
+
|
23
|
+
traces = []
|
24
|
+
for label, series in data.items():
|
25
|
+
traces.append(go.Scatter(
|
26
|
+
x=series["x"],
|
27
|
+
y=series["y"],
|
28
|
+
mode="lines+markers",
|
29
|
+
name=label
|
30
|
+
))
|
31
|
+
|
32
|
+
layout = go.Layout(
|
33
|
+
title="EDS Data Plot (Static)",
|
34
|
+
margin=dict(t=40)
|
35
|
+
)
|
36
|
+
|
37
|
+
fig = go.Figure(data=traces, layout=layout)
|
38
|
+
|
39
|
+
# Write to a temporary HTML file
|
40
|
+
tmp_file = tempfile.NamedTemporaryFile(suffix=".html", delete=False)
|
41
|
+
pyo.plot(fig, filename=tmp_file.name, auto_open=False)
|
42
|
+
|
43
|
+
webbrowser.open(f"file://{tmp_file.name}")
|
pipeline/workspace_manager.py
CHANGED
@@ -2,6 +2,8 @@ import os
|
|
2
2
|
import toml
|
3
3
|
import logging
|
4
4
|
from pathlib import Path
|
5
|
+
import sys
|
6
|
+
import mulch
|
5
7
|
|
6
8
|
'''
|
7
9
|
Goal:
|
@@ -29,7 +31,14 @@ class WorkspaceManager:
|
|
29
31
|
APP_NAME = "pipeline"
|
30
32
|
|
31
33
|
TIMESTAMPS_JSON_FILE_NAME = 'timestamps_success.json'
|
32
|
-
|
34
|
+
|
35
|
+
# Detect if running in a dev repo vs installed package
|
36
|
+
if getattr(sys, "frozen", False):
|
37
|
+
# Running from a pipx/executable environment
|
38
|
+
ROOT_DIR = None
|
39
|
+
else:
|
40
|
+
# Running from a cloned repo
|
41
|
+
ROOT_DIR = Path(__file__).resolve().parents[2] # root directory
|
33
42
|
|
34
43
|
|
35
44
|
# This climbs out of /src/pipeline/ to find the root.
|
@@ -62,11 +71,38 @@ class WorkspaceManager:
|
|
62
71
|
self.logs_dir,
|
63
72
|
self.aggregate_dir])
|
64
73
|
|
65
|
-
|
66
|
-
|
74
|
+
|
75
|
+
@classmethod
|
76
|
+
def get_workspaces_dir(cls):
|
77
|
+
"""
|
78
|
+
Return workspaces directory depending on environment:
|
79
|
+
- If ROOT_DIR is defined (repo clone), use that
|
80
|
+
- Else use AppData/local platform-specific location
|
81
|
+
"""
|
82
|
+
if cls.ROOT_DIR and (cls.ROOT_DIR / cls.WORKSPACES_DIR_NAME).exists():
|
83
|
+
workspaces_dir = cls.ROOT_DIR / cls.WORKSPACES_DIR_NAME
|
84
|
+
else:
|
85
|
+
workspaces_dir = cls.get_appdata_dir() / cls.WORKSPACES_DIR_NAME
|
86
|
+
workspaces_dir.mkdir(parents=True, exist_ok=True)
|
87
|
+
default_file = workspaces_dir / cls.DEFAULT_WORKSPACE_TOML_FILE_NAME
|
88
|
+
if not default_file.exists():
|
89
|
+
# auto-populate default TOML with most recent workspace
|
90
|
+
recent_ws = cls.most_recent_workspace_name() or "default"
|
91
|
+
default_file.write_text(f"[default-workspace]\nworkspace = '{recent_ws}'\n")
|
92
|
+
return workspaces_dir
|
93
|
+
|
94
|
+
@classmethod
|
95
|
+
def most_recent_workspace_name(cls):
|
96
|
+
workspaces_dir = cls.get_workspaces_dir()
|
97
|
+
all_dirs = [p for p in workspaces_dir.iterdir() if p.is_dir() and not p.name.startswith('.')]
|
98
|
+
if not all_dirs:
|
99
|
+
return None
|
100
|
+
latest = max(all_dirs, key=lambda p: p.stat().st_mtime)
|
101
|
+
return latest.name
|
67
102
|
|
68
103
|
def get_workspace_dir(self):
|
69
|
-
|
104
|
+
# workspace_name is established at instantiation. You want a new name? Initialize a new WorkspaceManager(). It manages one workpspace.
|
105
|
+
return self.get_workspaces_dir() / self.workspace_name
|
70
106
|
|
71
107
|
def get_exports_dir(self):
|
72
108
|
return self.workspace_dir / self.EXPORTS_DIR_NAME
|
@@ -185,24 +221,45 @@ class WorkspaceManager:
|
|
185
221
|
"""
|
186
222
|
Class method that reads default-workspace.toml to identify the default-workspace path.
|
187
223
|
"""
|
188
|
-
|
224
|
+
|
189
225
|
workspaces_dir = cls.ROOT_DIR / cls.WORKSPACES_DIR_NAME
|
190
|
-
|
191
|
-
if
|
192
|
-
|
193
|
-
|
226
|
+
workspace_name = cls.identify_default_workspace_name()
|
227
|
+
if workspace_name is None:
|
228
|
+
workspace_name = cls.most_recent_workspace_name() # if
|
229
|
+
if workspace_name is None:
|
230
|
+
mulch.workspace(target_dir = workspaces_dir, scaffold = workspaces_dir / '.mulch' / 'mulch.toml', workspace_name = 'eds') # allow date based default if no workspace_name is provided
|
231
|
+
workspace_name = 'eds'
|
232
|
+
workspace_path = workspaces_dir / workspace_name
|
233
|
+
if not workspace_path.exists():
|
234
|
+
#raise FileNotFoundError(f"Workspace directory not found: {default_workspace_path}")
|
235
|
+
print("No default_workspace.toml file to identify a default workspace folder, so the most recently edited folder will be used.")
|
236
|
+
|
237
|
+
return workspace_path
|
238
|
+
|
239
|
+
'''
|
240
|
+
@classmethod
|
241
|
+
def identify_default_workspace_name(cls, workspaces_dir = None):
|
242
|
+
if workspaces_dir is None:
|
243
|
+
workspaces_dir = cls.get_workspaces_dir()
|
244
|
+
default_file = workspaces_dir / cls.DEFAULT_WORKSPACE_TOML_FILE_NAME
|
245
|
+
if not default_file.exists():
|
246
|
+
default_file.write_text("# Default workspace\n")
|
247
|
+
return str(default_file)
|
248
|
+
'''
|
249
|
+
|
194
250
|
@classmethod
|
195
|
-
def identify_default_workspace_name(cls):
|
251
|
+
def identify_default_workspace_name(cls, workspaces_dir = None):
|
196
252
|
"""
|
197
253
|
Class method that reads default-workspace.toml to identify the default-workspace.
|
198
254
|
"""
|
199
|
-
|
200
|
-
|
255
|
+
if workspaces_dir is None:
|
256
|
+
workspaces_dir = cls.get_workspaces_dir()
|
201
257
|
logging.info(f"workspaces_dir = {workspaces_dir}\n")
|
202
258
|
default_toml_path = workspaces_dir / cls.DEFAULT_WORKSPACE_TOML_FILE_NAME
|
203
259
|
|
204
260
|
if not default_toml_path.exists():
|
205
|
-
|
261
|
+
return None
|
262
|
+
#raise FileNotFoundError(f"Missing {cls.DEFAULT_WORKSPACE_TOML_FILE_NAME} in {workspaces_dir}")
|
206
263
|
|
207
264
|
with open(default_toml_path, 'r') as f:
|
208
265
|
data = toml.load(f)
|
@@ -244,16 +301,19 @@ class WorkspaceManager:
|
|
244
301
|
return base / cls.APP_NAME
|
245
302
|
|
246
303
|
@classmethod
|
247
|
-
def
|
304
|
+
def ensure_appdata_workspaces_dir(cls) -> Path:
|
248
305
|
"""Create workspace folder and default toml if missing."""
|
249
|
-
workspaces_dir = cls.get_appdata_dir() /
|
306
|
+
workspaces_dir = cls.get_appdata_dir() / cls.WORKSPACES_DIR_NAME
|
250
307
|
workspaces_dir.mkdir(parents=True, exist_ok=True)
|
251
|
-
|
308
|
+
cls.workspaces_dir = workspaces_dir
|
252
309
|
default_file = workspaces_dir / cls.DEFAULT_WORKSPACE_TOML_FILE_NAME
|
253
310
|
if not default_file.exists():
|
254
|
-
|
311
|
+
pass
|
312
|
+
#default_file.write_text("# Default workspace config\n")
|
313
|
+
mulch_scaffold_toml = []
|
314
|
+
mulch.seed(target_dir = workspaces_dir,scaffold = mulch_scaffold_toml)
|
315
|
+
mulch.workspace(base_dir = workspaces_dir.parent, scaffold_file = workspaces_dir / '.mulch' / 'mulch.toml', workspace_name = 'eds') # allow date based default if no workspace_name is provided
|
255
316
|
return workspaces_dir
|
256
|
-
|
257
317
|
|
258
318
|
def establish_default_workspace():
|
259
319
|
workspace_name = WorkspaceManager.identify_default_workspace_name()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: pipeline-eds
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.16
|
4
4
|
Summary: The official API pipeline library for mulch-based projects. Key target: Emerson Ovation EDS REST API.
|
5
5
|
License: BSD-3
|
6
6
|
Author: George Clayton Bennett
|
@@ -13,7 +13,8 @@ Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
14
14
|
Provides-Extra: windb
|
15
15
|
Requires-Dist: certifi (>=2025.1.31,<2026.0.0)
|
16
|
-
Requires-Dist: fastapi (
|
16
|
+
Requires-Dist: fastapi (>=0.116.1,<0.117.0)
|
17
|
+
Requires-Dist: matplotlib (>=3.10.6,<4.0.0)
|
17
18
|
Requires-Dist: mulch (>=0.2.8,<0.3.0)
|
18
19
|
Requires-Dist: mysql-connector-python (>=9.3.0,<10.0.0)
|
19
20
|
Requires-Dist: pendulum (>=3.1.0,<4.0.0)
|
@@ -1,17 +1,18 @@
|
|
1
1
|
pipeline/__init__.py,sha256=DAy4hBleDDk9Wen4qIOGlS03qWqdt7K7n4rUpL3cIL4,173
|
2
2
|
pipeline/__main__.py,sha256=TtbGYUm7Np5eLPCHZ3iX5QPL0rSgGJ5tXVuBEORvQIE,19
|
3
3
|
pipeline/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
-
pipeline/api/eds.py,sha256=
|
4
|
+
pipeline/api/eds.py,sha256=O6kHH9KsBKIXiLrT0Cj84g_UUIhjlK8vPPJbG2MP0DU,45087
|
5
5
|
pipeline/api/rjn.py,sha256=Fhbc3tE_WgUywVJimnLzhWnxyMzGiIUu54hRFR3sdUA,7102
|
6
6
|
pipeline/api/status_api.py,sha256=KJG7e4GXmTjqQxK3LFUcdAxj1jqzvNLJ0f9-tuisk00,202
|
7
7
|
pipeline/calls.py,sha256=FexXJK0_fwgQMPx9dy5eFai_6xqVOsOGoEUw8yP3eSU,4187
|
8
|
-
pipeline/cli.py,sha256=
|
8
|
+
pipeline/cli.py,sha256=QTyJx5MzcF-b1IB2sjg4GsbrhTucb83tQifSxosleRc,11852
|
9
9
|
pipeline/configrationmanager.py,sha256=UPqa91117jNm59vvTBE7sET1ThisqRf6B2Dhtk7x9tM,624
|
10
10
|
pipeline/decorators.py,sha256=5fIIVqxSvQFaSI4ZkqPd3yqajzDxaRhgYwlC1jD2k5A,411
|
11
|
-
pipeline/env.py,sha256=
|
12
|
-
pipeline/environment.py,sha256=
|
13
|
-
pipeline/gui_fastapi_plotly_live.py,sha256=
|
14
|
-
pipeline/gui_mpl_live.py,sha256=
|
11
|
+
pipeline/env.py,sha256=aVYssr6b5fVXt1GxehloyOv7fbRmuUzSQ7uQz4zwI_0,1926
|
12
|
+
pipeline/environment.py,sha256=ABXv5DAWW0EGrnE27dJNkj_aKmyD6e84RDSMyW1qKzE,1523
|
13
|
+
pipeline/gui_fastapi_plotly_live.py,sha256=bje-W93yWb7G7A5H7Rg7BgNpfV_fjxhbl7x7zwkUCXc,3246
|
14
|
+
pipeline/gui_mpl_live.py,sha256=JLh2YWK2SwGykik7mC_HfNMVzVVYymoEnwzSB7VHrIk,4286
|
15
|
+
pipeline/gui_plotly_static.py,sha256=wUNTjLqsuldWRYu65w5-HDco4201FKAO5esMe2NOras,1133
|
15
16
|
pipeline/helpers.py,sha256=-GGFz5t22wlFfHwtEDwWOBslzguLXXSYNzxxRBvclLI,4457
|
16
17
|
pipeline/install_appdata.py,sha256=DGemGRogMVAmFzqSKladfxgLQOslIHyul2Xxq34do8A,2814
|
17
18
|
pipeline/logging_setup.py,sha256=CAqWfchscCdzJ63Wf1dC3QGRnFnQqBELxyTmPZxsxgs,1674
|
@@ -21,7 +22,7 @@ pipeline/plotbuffer.py,sha256=jCsFbT47TdR8Sq5tjj2JdhVELjRiebLPN7O4r2LjPeY,625
|
|
21
22
|
pipeline/points_loader.py,sha256=4OCGLiatbP3D5hixVnYcFGThvBRYt_bf5DhNGdGw_9k,519
|
22
23
|
pipeline/queriesmanager.py,sha256=QwPhDV39Z8mdAVDRXTF4ZPgc6JliMgLzucGaGZSlDac,5001
|
23
24
|
pipeline/time_manager.py,sha256=gSK430SyHvhgUWLRg_z2nBiyad01v7ByyKafB138IkU,8351
|
24
|
-
pipeline/workspace_manager.py,sha256=
|
25
|
+
pipeline/workspace_manager.py,sha256=3EN0sqJDdv2C1keAz74uqloNv7aDHc76WKaLXNSRXRg,15090
|
25
26
|
workspaces/default-workspace.toml,sha256=dI8y2l2WlEbIck6IZpbuQUP8-Bf48bBE1CKKsnVMc8w,300
|
26
27
|
workspaces/eds_to_rjn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
27
28
|
workspaces/eds_to_rjn/code/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -56,8 +57,8 @@ workspaces/eds_to_rjn/scripts/daemon_runner.py,sha256=gBCYrJ-FYPCTt_l5O07_YNrrGj
|
|
56
57
|
workspaces/eds_to_rjn/secrets/README.md,sha256=tWf2bhopA0C08C8ImtHNZoPde9ub-sLMjX6EMe7lyJw,600
|
57
58
|
workspaces/eds_to_rjn/secrets/secrets-example.yaml,sha256=qKGrKsKBC0ulDQRVbr1zkfNlr8WPWK4lg5GAvTqZ-T4,365
|
58
59
|
workspaces/eds_to_termux/..txt,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
59
|
-
pipeline_eds-0.2.
|
60
|
-
pipeline_eds-0.2.
|
61
|
-
pipeline_eds-0.2.
|
62
|
-
pipeline_eds-0.2.
|
63
|
-
pipeline_eds-0.2.
|
60
|
+
pipeline_eds-0.2.16.dist-info/entry_points.txt,sha256=jmU0FQ7-2AHXhKcj4TXPn61xLbHlycHA2lkDlRZT-pg,124
|
61
|
+
pipeline_eds-0.2.16.dist-info/LICENSE,sha256=LKdx0wS1t9vFZpbRhDg_iLQ6ny-XsXRwhKAoCfrF6iA,1501
|
62
|
+
pipeline_eds-0.2.16.dist-info/METADATA,sha256=kpGXHqeZrM22IGgaDwRksLA8g_sAMN_-9qO1eUmBk80,10027
|
63
|
+
pipeline_eds-0.2.16.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
64
|
+
pipeline_eds-0.2.16.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|