FlowerPower 0.9.12.4__py3-none-any.whl → 1.0.0b1__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.
- flowerpower/__init__.py +17 -2
- flowerpower/cfg/__init__.py +201 -149
- flowerpower/cfg/base.py +122 -24
- flowerpower/cfg/pipeline/__init__.py +254 -0
- flowerpower/cfg/pipeline/adapter.py +66 -0
- flowerpower/cfg/pipeline/run.py +40 -11
- flowerpower/cfg/pipeline/schedule.py +69 -79
- flowerpower/cfg/project/__init__.py +149 -0
- flowerpower/cfg/project/adapter.py +57 -0
- flowerpower/cfg/project/job_queue.py +165 -0
- flowerpower/cli/__init__.py +92 -35
- flowerpower/cli/job_queue.py +878 -0
- flowerpower/cli/mqtt.py +49 -4
- flowerpower/cli/pipeline.py +576 -381
- flowerpower/cli/utils.py +55 -0
- flowerpower/flowerpower.py +12 -7
- flowerpower/fs/__init__.py +20 -2
- flowerpower/fs/base.py +350 -26
- flowerpower/fs/ext.py +797 -216
- flowerpower/fs/storage_options.py +1097 -55
- flowerpower/io/base.py +13 -18
- flowerpower/io/loader/__init__.py +28 -0
- flowerpower/io/loader/deltatable.py +7 -10
- flowerpower/io/metadata.py +1 -0
- flowerpower/io/saver/__init__.py +28 -0
- flowerpower/io/saver/deltatable.py +4 -3
- flowerpower/job_queue/__init__.py +252 -0
- flowerpower/job_queue/apscheduler/__init__.py +11 -0
- flowerpower/job_queue/apscheduler/_setup/datastore.py +110 -0
- flowerpower/job_queue/apscheduler/_setup/eventbroker.py +93 -0
- flowerpower/job_queue/apscheduler/manager.py +1063 -0
- flowerpower/job_queue/apscheduler/setup.py +524 -0
- flowerpower/job_queue/apscheduler/trigger.py +169 -0
- flowerpower/job_queue/apscheduler/utils.py +309 -0
- flowerpower/job_queue/base.py +382 -0
- flowerpower/job_queue/rq/__init__.py +10 -0
- flowerpower/job_queue/rq/_trigger.py +37 -0
- flowerpower/job_queue/rq/concurrent_workers/gevent_worker.py +226 -0
- flowerpower/job_queue/rq/concurrent_workers/thread_worker.py +231 -0
- flowerpower/job_queue/rq/manager.py +1449 -0
- flowerpower/job_queue/rq/setup.py +150 -0
- flowerpower/job_queue/rq/utils.py +69 -0
- flowerpower/pipeline/__init__.py +5 -0
- flowerpower/pipeline/base.py +118 -0
- flowerpower/pipeline/io.py +407 -0
- flowerpower/pipeline/job_queue.py +505 -0
- flowerpower/pipeline/manager.py +1586 -0
- flowerpower/pipeline/registry.py +560 -0
- flowerpower/pipeline/runner.py +560 -0
- flowerpower/pipeline/visualizer.py +142 -0
- flowerpower/plugins/mqtt/__init__.py +12 -0
- flowerpower/plugins/mqtt/cfg.py +16 -0
- flowerpower/plugins/mqtt/manager.py +789 -0
- flowerpower/settings.py +110 -0
- flowerpower/utils/logging.py +21 -0
- flowerpower/utils/misc.py +57 -9
- flowerpower/utils/sql.py +122 -24
- flowerpower/utils/templates.py +18 -142
- flowerpower/web/app.py +0 -0
- flowerpower-1.0.0b1.dist-info/METADATA +324 -0
- flowerpower-1.0.0b1.dist-info/RECORD +94 -0
- {flowerpower-0.9.12.4.dist-info → flowerpower-1.0.0b1.dist-info}/WHEEL +1 -1
- flowerpower/cfg/pipeline/tracker.py +0 -14
- flowerpower/cfg/project/open_telemetry.py +0 -8
- flowerpower/cfg/project/tracker.py +0 -11
- flowerpower/cfg/project/worker.py +0 -19
- flowerpower/cli/scheduler.py +0 -309
- flowerpower/event_handler.py +0 -23
- flowerpower/mqtt.py +0 -525
- flowerpower/pipeline.py +0 -2419
- flowerpower/scheduler.py +0 -680
- flowerpower/tui.py +0 -79
- flowerpower/utils/datastore.py +0 -186
- flowerpower/utils/eventbroker.py +0 -127
- flowerpower/utils/executor.py +0 -58
- flowerpower/utils/trigger.py +0 -140
- flowerpower-0.9.12.4.dist-info/METADATA +0 -575
- flowerpower-0.9.12.4.dist-info/RECORD +0 -70
- /flowerpower/{cfg/pipeline/params.py → cli/worker.py} +0 -0
- {flowerpower-0.9.12.4.dist-info → flowerpower-1.0.0b1.dist-info}/entry_points.txt +0 -0
- {flowerpower-0.9.12.4.dist-info → flowerpower-1.0.0b1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,560 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
"""Pipeline Registry for discovery, listing, creation, and deletion."""
|
3
|
+
import os
|
4
|
+
import datetime as dt
|
5
|
+
import posixpath
|
6
|
+
from typing import TYPE_CHECKING
|
7
|
+
|
8
|
+
import rich
|
9
|
+
from loguru import logger
|
10
|
+
from rich.console import Console
|
11
|
+
from rich.panel import Panel
|
12
|
+
from rich.syntax import Syntax
|
13
|
+
from rich.table import Table
|
14
|
+
from rich.tree import Tree
|
15
|
+
|
16
|
+
from .. import settings
|
17
|
+
# Import necessary config types and utility functions
|
18
|
+
from ..cfg import PipelineConfig, ProjectConfig
|
19
|
+
from ..fs import AbstractFileSystem
|
20
|
+
from ..utils.logging import setup_logging
|
21
|
+
# Assuming view_img might be used indirectly or needed later
|
22
|
+
from ..utils.templates import PIPELINE_PY_TEMPLATE, HOOK_TEMPLATE__MQTT_BUILD_CONFIG
|
23
|
+
|
24
|
+
if TYPE_CHECKING:
|
25
|
+
# Keep this for type hinting if needed elsewhere, though Config is imported directly now
|
26
|
+
pass
|
27
|
+
|
28
|
+
from enum import Enum
|
29
|
+
|
30
|
+
class HookType(str, Enum):
|
31
|
+
MQTT_BUILD_CONFIG = "mqtt-build-config"
|
32
|
+
|
33
|
+
def default_function_name(self) -> str:
|
34
|
+
match self.value:
|
35
|
+
case HookType.MQTT_BUILD_CONFIG:
|
36
|
+
return self.value.replace("-", "_")
|
37
|
+
case _:
|
38
|
+
return self.value
|
39
|
+
|
40
|
+
def __str__(self) -> str:
|
41
|
+
return self.value
|
42
|
+
|
43
|
+
setup_logging(level=settings.LOG_LEVEL)
|
44
|
+
|
45
|
+
|
46
|
+
class PipelineRegistry:
|
47
|
+
"""Manages discovery, listing, creation, and deletion of pipelines."""
|
48
|
+
|
49
|
+
def __init__(
|
50
|
+
self,
|
51
|
+
project_cfg: ProjectConfig,
|
52
|
+
fs: AbstractFileSystem,
|
53
|
+
cfg_dir: str,
|
54
|
+
pipelines_dir: str,
|
55
|
+
):
|
56
|
+
"""
|
57
|
+
Initializes the PipelineRegistry.
|
58
|
+
|
59
|
+
Args:
|
60
|
+
project_cfg: The project configuration object.
|
61
|
+
fs: The filesystem instance.
|
62
|
+
cfg_dir: The configuration directory path.
|
63
|
+
pipelines_dir: The pipelines directory path.
|
64
|
+
"""
|
65
|
+
self.project_cfg = project_cfg
|
66
|
+
self._fs = fs
|
67
|
+
self._cfg_dir = cfg_dir
|
68
|
+
self._pipelines_dir = pipelines_dir
|
69
|
+
self._console = Console()
|
70
|
+
|
71
|
+
# --- Methods moved from PipelineManager ---
|
72
|
+
def new(self, name: str, overwrite: bool = False):
|
73
|
+
"""
|
74
|
+
Adds a pipeline with the given name.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
name (str): The name of the pipeline.
|
78
|
+
overwrite (bool): Whether to overwrite an existing pipeline. Defaults to False.
|
79
|
+
job_queue_type (str | None): The type of worker to use. Defaults to None.
|
80
|
+
|
81
|
+
Raises:
|
82
|
+
ValueError: If the configuration or pipeline path does not exist, or if the pipeline already exists.
|
83
|
+
|
84
|
+
Examples:
|
85
|
+
>>> pm = PipelineManager()
|
86
|
+
>>> pm.new("my_pipeline")
|
87
|
+
"""
|
88
|
+
# Use attributes derived from self.project_cfg
|
89
|
+
for dir_path, label in (
|
90
|
+
(self._cfg_dir, "configuration"),
|
91
|
+
(self._pipelines_dir, "pipeline"),
|
92
|
+
):
|
93
|
+
if not self._fs.exists(dir_path):
|
94
|
+
raise ValueError(
|
95
|
+
f"{label.capitalize()} path {dir_path} does not exist. Please run flowerpower init first."
|
96
|
+
)
|
97
|
+
|
98
|
+
formatted_name = name.replace(".", "/").replace("-", "_")
|
99
|
+
pipeline_file = posixpath.join(self._pipelines_dir, f"{formatted_name}.py")
|
100
|
+
cfg_file = posixpath.join(self._cfg_dir, "pipelines", f"{formatted_name}.yml")
|
101
|
+
|
102
|
+
def check_and_handle(path: str):
|
103
|
+
if self._fs.exists(path):
|
104
|
+
if overwrite:
|
105
|
+
self._fs.rm(path)
|
106
|
+
else:
|
107
|
+
raise ValueError(
|
108
|
+
f"Pipeline {self.project_cfg.name}.{formatted_name} already exists. Use `overwrite=True` to overwrite."
|
109
|
+
)
|
110
|
+
|
111
|
+
check_and_handle(pipeline_file)
|
112
|
+
check_and_handle(cfg_file)
|
113
|
+
|
114
|
+
# Ensure directories for the new files exist
|
115
|
+
for file_path in (pipeline_file, cfg_file):
|
116
|
+
self._fs.makedirs(file_path.rsplit("/", 1)[0], exist_ok=True)
|
117
|
+
|
118
|
+
# Write pipeline code template
|
119
|
+
with self._fs.open(pipeline_file, "w") as f:
|
120
|
+
f.write(
|
121
|
+
PIPELINE_PY_TEMPLATE.format(
|
122
|
+
name=name,
|
123
|
+
date=dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
124
|
+
)
|
125
|
+
)
|
126
|
+
|
127
|
+
# Create default pipeline config and save it directly
|
128
|
+
new_pipeline_cfg = PipelineConfig(name=name)
|
129
|
+
new_pipeline_cfg.save(fs=self._fs) # Save only the pipeline part
|
130
|
+
|
131
|
+
rich.print(
|
132
|
+
f"🔧 Created new pipeline [bold blue]{self.project_cfg.name}.{name}[/bold blue]"
|
133
|
+
)
|
134
|
+
|
135
|
+
def delete(self, name: str, cfg: bool = True, module: bool = False):
|
136
|
+
"""
|
137
|
+
Delete a pipeline.
|
138
|
+
|
139
|
+
Args:
|
140
|
+
name (str): The name of the pipeline.
|
141
|
+
cfg (bool, optional): Whether to delete the config file. Defaults to True.
|
142
|
+
module (bool, optional): Whether to delete the module file. Defaults to False.
|
143
|
+
|
144
|
+
Returns:
|
145
|
+
None
|
146
|
+
|
147
|
+
Raises:
|
148
|
+
FileNotFoundError: If the specified files do not exist.
|
149
|
+
|
150
|
+
Examples:
|
151
|
+
>>> pm = PipelineManager()
|
152
|
+
>>> pm.delete("my_pipeline")
|
153
|
+
"""
|
154
|
+
deleted_files = []
|
155
|
+
if cfg:
|
156
|
+
pipeline_cfg_path = posixpath.join(
|
157
|
+
self._cfg_dir, "pipelines", f"{name}.yml"
|
158
|
+
)
|
159
|
+
if self._fs.exists(pipeline_cfg_path):
|
160
|
+
self._fs.rm(pipeline_cfg_path)
|
161
|
+
deleted_files.append(pipeline_cfg_path)
|
162
|
+
logger.debug(
|
163
|
+
f"Deleted pipeline config: {pipeline_cfg_path}"
|
164
|
+
) # Changed to DEBUG
|
165
|
+
else:
|
166
|
+
logger.warning(
|
167
|
+
f"Config file not found, skipping deletion: {pipeline_cfg_path}"
|
168
|
+
)
|
169
|
+
|
170
|
+
if module:
|
171
|
+
pipeline_py_path = posixpath.join(self._pipelines_dir, f"{name}.py")
|
172
|
+
if self._fs.exists(pipeline_py_path):
|
173
|
+
self._fs.rm(pipeline_py_path)
|
174
|
+
deleted_files.append(pipeline_py_path)
|
175
|
+
logger.debug(
|
176
|
+
f"Deleted pipeline module: {pipeline_py_path}"
|
177
|
+
) # Changed to DEBUG
|
178
|
+
else:
|
179
|
+
logger.warning(
|
180
|
+
f"Module file not found, skipping deletion: {pipeline_py_path}"
|
181
|
+
)
|
182
|
+
|
183
|
+
if not deleted_files:
|
184
|
+
logger.warning(
|
185
|
+
f"No files found or specified for deletion for pipeline '{name}'."
|
186
|
+
)
|
187
|
+
|
188
|
+
# Sync filesystem if needed (using _fs)
|
189
|
+
if hasattr(self._fs, "sync") and callable(getattr(self._fs, "sync")):
|
190
|
+
self._fs.sync()
|
191
|
+
|
192
|
+
def _get_files(self) -> list[str]:
|
193
|
+
"""
|
194
|
+
Get the list of pipeline files.
|
195
|
+
|
196
|
+
Returns:
|
197
|
+
list[str]: The list of pipeline files.
|
198
|
+
"""
|
199
|
+
try:
|
200
|
+
return self._fs.glob(posixpath.join(self._pipelines_dir, "*.py"))
|
201
|
+
except Exception as e:
|
202
|
+
logger.error(
|
203
|
+
f"Error accessing pipeline directory {self._pipelines_dir}: {e}"
|
204
|
+
)
|
205
|
+
return []
|
206
|
+
|
207
|
+
def _get_names(self) -> list[str]:
|
208
|
+
"""
|
209
|
+
Get the list of pipeline names.
|
210
|
+
|
211
|
+
Returns:
|
212
|
+
list[str]: The list of pipeline names.
|
213
|
+
"""
|
214
|
+
files = self._get_files()
|
215
|
+
return [posixpath.basename(f).replace(".py", "") for f in files]
|
216
|
+
|
217
|
+
def get_summary(
|
218
|
+
self,
|
219
|
+
name: str | None = None,
|
220
|
+
cfg: bool = True,
|
221
|
+
code: bool = True,
|
222
|
+
project: bool = True,
|
223
|
+
) -> dict[str, dict | str]:
|
224
|
+
"""
|
225
|
+
Get a summary of the pipelines.
|
226
|
+
|
227
|
+
Args:
|
228
|
+
name (str | None, optional): The name of the pipeline. Defaults to None.
|
229
|
+
cfg (bool, optional): Whether to show the configuration. Defaults to True.
|
230
|
+
code (bool, optional): Whether to show the module. Defaults to True.
|
231
|
+
project (bool, optional): Whether to show the project configuration. Defaults to True.
|
232
|
+
Returns:
|
233
|
+
dict[str, dict | str]: A dictionary containing the pipeline summary.
|
234
|
+
|
235
|
+
Examples:
|
236
|
+
```python
|
237
|
+
pm = PipelineManager()
|
238
|
+
summary=pm.get_summary()
|
239
|
+
```
|
240
|
+
"""
|
241
|
+
if name:
|
242
|
+
pipeline_names = [name]
|
243
|
+
else:
|
244
|
+
pipeline_names = self._get_names()
|
245
|
+
|
246
|
+
summary = {}
|
247
|
+
summary["pipelines"] = {}
|
248
|
+
|
249
|
+
if project:
|
250
|
+
# Use self.project_cfg directly
|
251
|
+
summary["project"] = self.project_cfg.to_dict()
|
252
|
+
|
253
|
+
for name in pipeline_names:
|
254
|
+
# Load pipeline config directly
|
255
|
+
|
256
|
+
pipeline_summary = {}
|
257
|
+
if cfg:
|
258
|
+
pipeline_cfg = PipelineConfig.load(name=name, fs=self._fs)
|
259
|
+
pipeline_summary["cfg"] = pipeline_cfg.to_dict()
|
260
|
+
if code:
|
261
|
+
try:
|
262
|
+
module_content = self._fs.cat(
|
263
|
+
posixpath.join(self._pipelines_dir, f"{name}.py")
|
264
|
+
).decode()
|
265
|
+
pipeline_summary["module"] = module_content
|
266
|
+
except FileNotFoundError:
|
267
|
+
logger.warning(f"Module file not found for pipeline '{name}'")
|
268
|
+
pipeline_summary["module"] = "# Module file not found"
|
269
|
+
except Exception as e:
|
270
|
+
logger.error(
|
271
|
+
f"Error reading module file for pipeline '{name}': {e}"
|
272
|
+
)
|
273
|
+
pipeline_summary["module"] = f"# Error reading module file: {e}"
|
274
|
+
|
275
|
+
if pipeline_summary: # Only add if cfg or code was requested and found
|
276
|
+
summary["pipelines"][name] = pipeline_summary
|
277
|
+
return summary
|
278
|
+
|
279
|
+
def show_summary(
|
280
|
+
self,
|
281
|
+
name: str | None = None,
|
282
|
+
cfg: bool = True,
|
283
|
+
code: bool = True,
|
284
|
+
project: bool = True,
|
285
|
+
to_html: bool = False,
|
286
|
+
to_svg: bool = False,
|
287
|
+
) -> None | str:
|
288
|
+
"""
|
289
|
+
Show a summary of the pipelines.
|
290
|
+
|
291
|
+
Args:
|
292
|
+
name (str | None, optional): The name of the pipeline. Defaults to None.
|
293
|
+
cfg (bool, optional): Whether to show the configuration. Defaults to True.
|
294
|
+
code (bool, optional): Whether to show the module. Defaults to True.
|
295
|
+
project (bool, optional): Whether to show the project configuration. Defaults to True.
|
296
|
+
to_html (bool, optional): Whether to export the summary to HTML. Defaults to False.
|
297
|
+
to_svg (bool, optional): Whether to export the summary to SVG. Defaults to False.
|
298
|
+
|
299
|
+
Returns:
|
300
|
+
None | str: The summary of the pipelines. If `to_html` is True, returns the HTML string.
|
301
|
+
If `to_svg` is True, returns the SVG string.
|
302
|
+
|
303
|
+
Examples:
|
304
|
+
```python
|
305
|
+
pm = PipelineManager()
|
306
|
+
pm.show_summary()
|
307
|
+
```
|
308
|
+
"""
|
309
|
+
|
310
|
+
summary = self.get_summary(name=name, cfg=cfg, code=code, project=project)
|
311
|
+
project_summary = summary.get("project", {})
|
312
|
+
pipeline_summary = summary["pipelines"]
|
313
|
+
|
314
|
+
def add_dict_to_tree(tree, dict_data, style="green"):
|
315
|
+
for key, value in dict_data.items():
|
316
|
+
if isinstance(value, dict):
|
317
|
+
branch = tree.add(f"[cyan]{key}:", style="bold cyan")
|
318
|
+
add_dict_to_tree(branch, value, style)
|
319
|
+
else:
|
320
|
+
tree.add(f"[cyan]{key}:[/] [green]{value}[/]")
|
321
|
+
|
322
|
+
console = Console()
|
323
|
+
|
324
|
+
if project:
|
325
|
+
# Create tree for project config
|
326
|
+
project_tree = Tree("📁 Project Configuration", style="bold magenta")
|
327
|
+
add_dict_to_tree(project_tree, project_summary)
|
328
|
+
|
329
|
+
# Print project configuration
|
330
|
+
console.print(
|
331
|
+
Panel(
|
332
|
+
project_tree,
|
333
|
+
title="Project Configuration",
|
334
|
+
border_style="blue",
|
335
|
+
padding=(2, 2),
|
336
|
+
)
|
337
|
+
)
|
338
|
+
console.print("\n")
|
339
|
+
|
340
|
+
for pipeline, info in pipeline_summary.items():
|
341
|
+
# Create tree for config
|
342
|
+
config_tree = Tree("📋 Pipeline Configuration", style="bold magenta")
|
343
|
+
add_dict_to_tree(config_tree, info["cfg"])
|
344
|
+
|
345
|
+
# Create syntax-highlighted code view
|
346
|
+
code_view = Syntax(
|
347
|
+
info["module"],
|
348
|
+
"python",
|
349
|
+
theme="default",
|
350
|
+
line_numbers=False,
|
351
|
+
word_wrap=True,
|
352
|
+
code_width=80,
|
353
|
+
padding=2,
|
354
|
+
)
|
355
|
+
|
356
|
+
if cfg:
|
357
|
+
# console.print(f"🔄 Pipeline: {pipeline}", style="bold blue")
|
358
|
+
console.print(
|
359
|
+
Panel(
|
360
|
+
config_tree,
|
361
|
+
title=f"🔄 Pipeline: {pipeline}",
|
362
|
+
subtitle="Configuration",
|
363
|
+
border_style="blue",
|
364
|
+
padding=(2, 2),
|
365
|
+
)
|
366
|
+
)
|
367
|
+
console.print("\n")
|
368
|
+
|
369
|
+
if code:
|
370
|
+
# console.print(f"🔄 Pipeline: {pipeline}", style="bold blue")
|
371
|
+
console.print(
|
372
|
+
Panel(
|
373
|
+
code_view,
|
374
|
+
title=f"🔄 Pipeline: {pipeline}",
|
375
|
+
subtitle="Module",
|
376
|
+
border_style="blue",
|
377
|
+
padding=(2, 2),
|
378
|
+
)
|
379
|
+
)
|
380
|
+
console.print("\n")
|
381
|
+
if to_html:
|
382
|
+
return console.export_html()
|
383
|
+
elif to_svg:
|
384
|
+
return console.export_svg()
|
385
|
+
|
386
|
+
@property
|
387
|
+
def summary(self) -> dict[str, dict | str]:
|
388
|
+
"""
|
389
|
+
Get a summary of the pipelines.
|
390
|
+
|
391
|
+
Returns:
|
392
|
+
dict: A dictionary containing the pipeline summary.
|
393
|
+
"""
|
394
|
+
return self.get_summary()
|
395
|
+
|
396
|
+
def _all_pipelines(
|
397
|
+
self, show: bool = True, to_html: bool = False, to_svg: bool = False
|
398
|
+
) -> list[str] | None:
|
399
|
+
"""
|
400
|
+
Print all available pipelines in a formatted table.
|
401
|
+
|
402
|
+
Args:
|
403
|
+
show (bool, optional): Whether to print the table. Defaults to True.
|
404
|
+
to_html (bool, optional): Whether to export the table to HTML. Defaults to False.
|
405
|
+
to_svg (bool, optional): Whether to export the table to SVG. Defaults to False.
|
406
|
+
|
407
|
+
Returns:
|
408
|
+
list[str] | None: A list of pipeline names if `show` is False.
|
409
|
+
|
410
|
+
Examples:
|
411
|
+
```python
|
412
|
+
pm = PipelineManager()
|
413
|
+
all_pipelines = pm._pipelines(show=False)
|
414
|
+
```
|
415
|
+
"""
|
416
|
+
if to_html or to_svg:
|
417
|
+
show = True
|
418
|
+
|
419
|
+
pipeline_files = [
|
420
|
+
f for f in self._fs.ls(self._pipelines_dir) if f.endswith(".py")
|
421
|
+
]
|
422
|
+
pipeline_names = [
|
423
|
+
posixpath.splitext(posixpath.basename(f))[0] for f in pipeline_files
|
424
|
+
] # Simplified name extraction
|
425
|
+
|
426
|
+
if not pipeline_files:
|
427
|
+
rich.print("[yellow]No pipelines found[/yellow]")
|
428
|
+
return [] # Return empty list for consistency
|
429
|
+
|
430
|
+
pipeline_info = []
|
431
|
+
|
432
|
+
for path, name in zip(pipeline_files, pipeline_names):
|
433
|
+
try:
|
434
|
+
mod_time = self._fs.modified(path).strftime("%Y-%m-%d %H:%M:%S")
|
435
|
+
except NotImplementedError:
|
436
|
+
mod_time = "N/A"
|
437
|
+
try:
|
438
|
+
size_bytes = self._fs.size(path)
|
439
|
+
size = f"{size_bytes / 1024:.1f} KB" if size_bytes else "0.0 KB"
|
440
|
+
except NotImplementedError:
|
441
|
+
size = "N/A"
|
442
|
+
except Exception as e:
|
443
|
+
logger.warning(f"Could not get size for {path}: {e}")
|
444
|
+
size = "Error"
|
445
|
+
|
446
|
+
pipeline_info.append({
|
447
|
+
"name": name,
|
448
|
+
"path": path,
|
449
|
+
"mod_time": mod_time,
|
450
|
+
"size": size,
|
451
|
+
})
|
452
|
+
|
453
|
+
if show:
|
454
|
+
table = Table(title="Available Pipelines")
|
455
|
+
table.add_column("Pipeline Name", style="blue")
|
456
|
+
table.add_column("Path", style="magenta")
|
457
|
+
table.add_column("Last Modified", style="green")
|
458
|
+
table.add_column("Size", style="cyan")
|
459
|
+
|
460
|
+
for info in pipeline_info:
|
461
|
+
table.add_row(
|
462
|
+
info["name"], info["path"], info["mod_time"], info["size"]
|
463
|
+
)
|
464
|
+
console = Console(record=True)
|
465
|
+
console.print(table)
|
466
|
+
if to_html:
|
467
|
+
return console.export_html()
|
468
|
+
elif to_svg:
|
469
|
+
return console.export_svg()
|
470
|
+
|
471
|
+
else:
|
472
|
+
return pipeline_info
|
473
|
+
|
474
|
+
def show_pipelines(self) -> None:
|
475
|
+
"""
|
476
|
+
Print all available pipelines in a formatted table.
|
477
|
+
|
478
|
+
Examples:
|
479
|
+
```python
|
480
|
+
pm = PipelineManager()
|
481
|
+
pm.show_pipelines()
|
482
|
+
```
|
483
|
+
"""
|
484
|
+
self._all_pipelines(show=True)
|
485
|
+
|
486
|
+
def list_pipelines(self) -> list[str]:
|
487
|
+
"""
|
488
|
+
Get a list of all available pipelines.
|
489
|
+
|
490
|
+
Returns:
|
491
|
+
list[str] | None: A list of pipeline names.
|
492
|
+
|
493
|
+
Examples:
|
494
|
+
```python
|
495
|
+
pm = PipelineManager()
|
496
|
+
pipelines = pm.list_pipelines()
|
497
|
+
```
|
498
|
+
"""
|
499
|
+
return self._all_pipelines(show=False)
|
500
|
+
|
501
|
+
@property
|
502
|
+
def pipelines(self) -> list[str]:
|
503
|
+
"""
|
504
|
+
Get a list of all available pipelines.
|
505
|
+
|
506
|
+
Returns:
|
507
|
+
list[str] | None: A list of pipeline names.
|
508
|
+
|
509
|
+
Examples:
|
510
|
+
```python
|
511
|
+
pm = PipelineManager()
|
512
|
+
pipelines = pm.pipelines
|
513
|
+
```
|
514
|
+
"""
|
515
|
+
return self._all_pipelines(show=False)
|
516
|
+
|
517
|
+
def add_hook(self, name: str, type: HookType, to: str | None = None, function_name: str|None = None):
|
518
|
+
"""
|
519
|
+
Add a hook to the pipeline module.
|
520
|
+
|
521
|
+
Args:
|
522
|
+
name (str): The name of the pipeline
|
523
|
+
type (HookType): The type of the hook.
|
524
|
+
to (str | None, optional): The name of the file to add the hook to. Defaults to the hook.py file in the pipelines hooks folder.
|
525
|
+
function_name (str | None, optional): The name of the function. If not provided uses default name of hook type.
|
526
|
+
|
527
|
+
Returns:
|
528
|
+
None
|
529
|
+
|
530
|
+
Examples:
|
531
|
+
```python
|
532
|
+
pm = PipelineManager()
|
533
|
+
pm.add_hook(HookType.PRE_EXECUTE)
|
534
|
+
```
|
535
|
+
"""
|
536
|
+
|
537
|
+
|
538
|
+
if to is None:
|
539
|
+
to = f"hooks/{name}/hook.py"
|
540
|
+
else:
|
541
|
+
to = f"hooks/{name}/{to}"
|
542
|
+
|
543
|
+
match type:
|
544
|
+
case HookType.MQTT_BUILD_CONFIG:
|
545
|
+
template = HOOK_TEMPLATE__MQTT_BUILD_CONFIG
|
546
|
+
|
547
|
+
if function_name is None:
|
548
|
+
function_name = type.default_function_name()
|
549
|
+
|
550
|
+
if not self._fs.exists(to):
|
551
|
+
self._fs.makedirs(os.path.dirname(to), exist_ok=True)
|
552
|
+
|
553
|
+
with self._fs.open(to, "a") as f:
|
554
|
+
f.write(
|
555
|
+
template.format(
|
556
|
+
function_name=function_name
|
557
|
+
)
|
558
|
+
)
|
559
|
+
|
560
|
+
rich.print(f"🔧 Added hook [bold blue]{type.value}[/bold blue] to {to} as {function_name} for {name}")
|